Merge branch 'develop' into feature/proceed-exit-while-open-order
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
from datetime import datetime, timedelta
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
@@ -11,6 +12,7 @@ import pytest
|
||||
from freqtrade.commands import (
|
||||
start_backtesting_show,
|
||||
start_convert_data,
|
||||
start_convert_db,
|
||||
start_convert_trades,
|
||||
start_create_userdir,
|
||||
start_download_data,
|
||||
@@ -19,6 +21,8 @@ from freqtrade.commands import (
|
||||
start_install_ui,
|
||||
start_list_data,
|
||||
start_list_exchanges,
|
||||
start_list_freqAI_models,
|
||||
start_list_hyperopt_loss_functions,
|
||||
start_list_markets,
|
||||
start_list_strategies,
|
||||
start_list_timeframes,
|
||||
@@ -30,20 +34,18 @@ from freqtrade.commands import (
|
||||
start_trading,
|
||||
start_webserver,
|
||||
)
|
||||
from freqtrade.commands.db_commands import start_convert_db
|
||||
from freqtrade.commands.deploy_ui import (
|
||||
clean_ui_subdir,
|
||||
download_and_install_ui,
|
||||
get_ui_download_url,
|
||||
read_ui_version,
|
||||
)
|
||||
from freqtrade.commands.list_commands import start_list_freqAI_models
|
||||
from freqtrade.configuration import setup_utils_configuration
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.persistence.models import init_db
|
||||
from freqtrade.persistence.pairlock_middleware import PairLocks
|
||||
from freqtrade.util import dt_floor_day, dt_now, dt_utc
|
||||
from freqtrade.util import dt_utc
|
||||
from tests.conftest import (
|
||||
CURRENT_TEST_STRATEGY,
|
||||
EXMS,
|
||||
@@ -570,7 +572,7 @@ def test_create_datadir_failed(caplog):
|
||||
assert log_has("`create-userdir` requires --userdir to be set.", caplog)
|
||||
|
||||
|
||||
def test_create_datadir(caplog, mocker):
|
||||
def test_create_datadir(mocker):
|
||||
cud = mocker.patch(
|
||||
"freqtrade.configuration.directory_operations.create_userdata_dir", MagicMock()
|
||||
)
|
||||
@@ -584,26 +586,46 @@ def test_create_datadir(caplog, mocker):
|
||||
assert csf.call_count == 1
|
||||
|
||||
|
||||
def test_start_new_strategy(mocker, caplog):
|
||||
wt_mock = mocker.patch.object(Path, "write_text", MagicMock())
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||
def test_start_new_strategy(caplog, user_dir):
|
||||
strategy_dir = user_dir / "strategies"
|
||||
strategy_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
assert strategy_dir.is_dir()
|
||||
args = ["new-strategy", "--strategy", "CoolNewStrategy"]
|
||||
start_new_strategy(get_args(args))
|
||||
assert strategy_dir.exists()
|
||||
assert (strategy_dir / "CoolNewStrategy.py").exists()
|
||||
|
||||
assert wt_mock.call_count == 1
|
||||
assert "CoolNewStrategy" in wt_mock.call_args_list[0][0][0]
|
||||
assert log_has_re("Writing strategy to .*", caplog)
|
||||
|
||||
mocker.patch("freqtrade.configuration.setup_utils_configuration")
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
with pytest.raises(
|
||||
OperationalException, match=r".* already exists. Please choose another Strategy Name\."
|
||||
):
|
||||
start_new_strategy(get_args(args))
|
||||
|
||||
args = ["new-strategy", "--strategy", "CoolNewStrategy", "--strategy-path", str(user_dir)]
|
||||
start_new_strategy(get_args(args))
|
||||
assert (user_dir / "CoolNewStrategy.py").exists()
|
||||
|
||||
def test_start_new_strategy_no_arg(mocker, caplog):
|
||||
# strategy-path that doesn't exist
|
||||
args = [
|
||||
"new-strategy",
|
||||
"--strategy",
|
||||
"CoolNewStrategy",
|
||||
"--strategy-path",
|
||||
str(user_dir / "nonexistent"),
|
||||
]
|
||||
start_new_strategy(get_args(args))
|
||||
assert (user_dir / "CoolNewStrategy.py").exists()
|
||||
|
||||
assert log_has_re("Creating strategy directory .*", caplog)
|
||||
assert (user_dir / "nonexistent").is_dir()
|
||||
assert (user_dir / "nonexistent" / "CoolNewStrategy.py").exists()
|
||||
|
||||
shutil.rmtree(str(user_dir))
|
||||
|
||||
|
||||
def test_start_new_strategy_no_arg():
|
||||
args = [
|
||||
"new-strategy",
|
||||
]
|
||||
@@ -756,7 +778,13 @@ def test_download_data_keyboardInterrupt(mocker, markets):
|
||||
assert dl_mock.call_count == 1
|
||||
|
||||
|
||||
def test_download_data_timerange(mocker, markets):
|
||||
@pytest.mark.parametrize("time", ["00:00", "00:03", "00:30", "23:56"])
|
||||
@pytest.mark.parametrize(
|
||||
"tzoffset",
|
||||
["00:00", "+01:00", "-01:00", "+05:00", "-05:00"],
|
||||
)
|
||||
def test_download_data_timerange(mocker, markets, time_machine, time, tzoffset):
|
||||
time_machine.move_to(f"2024-11-01 {time}:00 {tzoffset}")
|
||||
dl_mock = mocker.patch(
|
||||
"freqtrade.data.history.history_utils.refresh_backtest_ohlcv_data",
|
||||
MagicMock(return_value=["ETH/BTC", "XRP/BTC"]),
|
||||
@@ -796,8 +824,9 @@ def test_download_data_timerange(mocker, markets):
|
||||
start_download_data(pargs)
|
||||
assert dl_mock.call_count == 1
|
||||
# 20days ago
|
||||
days_ago = dt_floor_day(dt_now() - timedelta(days=20)).timestamp()
|
||||
assert dl_mock.call_args_list[0][1]["timerange"].startts == days_ago
|
||||
days_ago = datetime.now() - timedelta(days=20)
|
||||
days_ago = dt_utc(days_ago.year, days_ago.month, days_ago.day)
|
||||
assert dl_mock.call_args_list[0][1]["timerange"].startts == days_ago.timestamp()
|
||||
|
||||
dl_mock.reset_mock()
|
||||
args = [
|
||||
@@ -818,28 +847,6 @@ def test_download_data_timerange(mocker, markets):
|
||||
assert dl_mock.call_args_list[0][1]["timerange"].startts == int(dt_utc(2020, 1, 1).timestamp())
|
||||
|
||||
|
||||
def test_download_data_no_markets(mocker, caplog):
|
||||
dl_mock = mocker.patch(
|
||||
"freqtrade.data.history.history_utils.refresh_backtest_ohlcv_data",
|
||||
MagicMock(return_value=["ETH/BTC", "XRP/BTC"]),
|
||||
)
|
||||
patch_exchange(mocker, exchange="binance")
|
||||
mocker.patch(f"{EXMS}.get_markets", return_value={})
|
||||
args = [
|
||||
"download-data",
|
||||
"--exchange",
|
||||
"binance",
|
||||
"--pairs",
|
||||
"ETH/BTC",
|
||||
"XRP/BTC",
|
||||
"--days",
|
||||
"20",
|
||||
]
|
||||
start_download_data(get_args(args))
|
||||
assert dl_mock.call_args[1]["timerange"].starttype == "date"
|
||||
assert log_has("Pairs [ETH/BTC,XRP/BTC] not available on exchange Binance.", caplog)
|
||||
|
||||
|
||||
def test_download_data_no_exchange(mocker):
|
||||
mocker.patch(
|
||||
"freqtrade.data.history.history_utils.refresh_backtest_ohlcv_data",
|
||||
@@ -918,7 +925,7 @@ def test_download_data_trades(mocker):
|
||||
"freqtrade.data.history.history_utils.convert_trades_to_ohlcv", MagicMock(return_value=[])
|
||||
)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch(f"{EXMS}.get_markets", return_value={})
|
||||
mocker.patch(f"{EXMS}.get_markets", return_value={"ETH/BTC": {}, "XRP/BTC": {}})
|
||||
args = [
|
||||
"download-data",
|
||||
"--exchange",
|
||||
@@ -953,7 +960,7 @@ def test_download_data_trades(mocker):
|
||||
|
||||
def test_download_data_data_invalid(mocker):
|
||||
patch_exchange(mocker, exchange="kraken")
|
||||
mocker.patch(f"{EXMS}.get_markets", return_value={})
|
||||
mocker.patch(f"{EXMS}.get_markets", return_value={"ETH/BTC": {}, "XRP/BTC": {}})
|
||||
args = [
|
||||
"download-data",
|
||||
"--exchange",
|
||||
@@ -1055,6 +1062,28 @@ def test_start_list_strategies(capsys):
|
||||
assert str(Path("broken_strats/broken_futures_strategies.py")) in captured.out
|
||||
|
||||
|
||||
def test_start_list_hyperopt_loss_functions(capsys):
|
||||
args = ["list-hyperoptloss", "-1"]
|
||||
pargs = get_args(args)
|
||||
pargs["config"] = None
|
||||
start_list_hyperopt_loss_functions(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert "CalmarHyperOptLoss" in captured.out
|
||||
assert "MaxDrawDownHyperOptLoss" in captured.out
|
||||
assert "SortinoHyperOptLossDaily" in captured.out
|
||||
assert "<builtin>/hyperopt_loss_sortino_daily.py" not in captured.out
|
||||
|
||||
args = ["list-hyperoptloss"]
|
||||
pargs = get_args(args)
|
||||
pargs["config"] = None
|
||||
start_list_hyperopt_loss_functions(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert "CalmarHyperOptLoss" in captured.out
|
||||
assert "MaxDrawDownHyperOptLoss" in captured.out
|
||||
assert "SortinoHyperOptLossDaily" in captured.out
|
||||
assert "<builtin>/hyperopt_loss_sortino_daily.py" in captured.out
|
||||
|
||||
|
||||
def test_start_list_freqAI_models(capsys):
|
||||
args = ["list-freqaimodels", "-1"]
|
||||
pargs = get_args(args)
|
||||
|
||||
+6
-7
@@ -5,7 +5,6 @@ import re
|
||||
from copy import deepcopy
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from unittest.mock import MagicMock, Mock, PropertyMock
|
||||
|
||||
import numpy as np
|
||||
@@ -124,7 +123,7 @@ def get_args(args):
|
||||
return Arguments(args).get_parsed_arg()
|
||||
|
||||
|
||||
def generate_trades_history(n_rows, start_date: Optional[datetime] = None, days=5):
|
||||
def generate_trades_history(n_rows, start_date: datetime | None = None, days=5):
|
||||
np.random.seed(42)
|
||||
if not start_date:
|
||||
start_date = datetime(2020, 1, 1, tzinfo=timezone.utc)
|
||||
@@ -206,7 +205,7 @@ def generate_test_data_raw(timeframe: str, size: int, start: str = "2020-07-05",
|
||||
"""Generates data in the ohlcv format used by ccxt"""
|
||||
df = generate_test_data(timeframe, size, start, random_seed)
|
||||
df["date"] = df.loc[:, "date"].astype(np.int64) // 1000 // 1000
|
||||
return list(list(x) for x in zip(*(df[x].values.tolist() for x in df.columns)))
|
||||
return list(list(x) for x in zip(*(df[x].values.tolist() for x in df.columns), strict=False))
|
||||
|
||||
|
||||
# Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines
|
||||
@@ -363,8 +362,8 @@ def patch_get_signal(
|
||||
exit_long=False,
|
||||
enter_short=False,
|
||||
exit_short=False,
|
||||
enter_tag: Optional[str] = None,
|
||||
exit_tag: Optional[str] = None,
|
||||
enter_tag: str | None = None,
|
||||
exit_tag: str | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param mocker: mocker to patch IStrategy class
|
||||
@@ -395,7 +394,7 @@ def patch_get_signal(
|
||||
freqtrade.exchange.refresh_latest_ohlcv = lambda p: None
|
||||
|
||||
|
||||
def create_mock_trades(fee, is_short: Optional[bool] = False, use_db: bool = True):
|
||||
def create_mock_trades(fee, is_short: bool | None = False, use_db: bool = True):
|
||||
"""
|
||||
Create some fake trades ...
|
||||
:param is_short: Optional bool, None creates a mix of long and short trades.
|
||||
@@ -474,7 +473,7 @@ def create_mock_trades_with_leverage(fee, use_db: bool = True):
|
||||
Trade.session.flush()
|
||||
|
||||
|
||||
def create_mock_trades_usdt(fee, is_short: Optional[bool] = False, use_db: bool = True):
|
||||
def create_mock_trades_usdt(fee, is_short: bool | None = False, use_db: bool = True):
|
||||
"""
|
||||
Create some fake trades ...
|
||||
"""
|
||||
|
||||
@@ -503,7 +503,7 @@ def test_calculate_max_drawdown2():
|
||||
]
|
||||
|
||||
dates = [dt_utc(2020, 1, 1) + timedelta(days=i) for i in range(len(values))]
|
||||
df = DataFrame(zip(values, dates), columns=["profit", "open_date"])
|
||||
df = DataFrame(zip(values, dates, strict=False), columns=["profit", "open_date"])
|
||||
# sort by profit and reset index
|
||||
df = df.sort_values("profit").reset_index(drop=True)
|
||||
df1 = df.copy()
|
||||
@@ -522,11 +522,11 @@ def test_calculate_max_drawdown2():
|
||||
assert drawdown.drawdown_abs == 0.091755
|
||||
assert pytest.approx(drawdown.relative_account_drawdown) == 0.32129575
|
||||
|
||||
df = DataFrame(zip(values[:5], dates[:5]), columns=["profit", "open_date"])
|
||||
df = DataFrame(zip(values[:5], dates[:5], strict=False), columns=["profit", "open_date"])
|
||||
with pytest.raises(ValueError, match="No losing trade, therefore no drawdown."):
|
||||
calculate_max_drawdown(df, date_col="open_date", value_col="profit")
|
||||
|
||||
df1 = DataFrame(zip(values[:5], dates[:5]), columns=["profit", "open_date"])
|
||||
df1 = DataFrame(zip(values[:5], dates[:5], strict=False), columns=["profit", "open_date"])
|
||||
df1.loc[:, "profit"] = df1["profit"] * -1
|
||||
# No winning trade ...
|
||||
drawdown = calculate_max_drawdown(df1, date_col="open_date", value_col="profit")
|
||||
@@ -548,7 +548,7 @@ def test_calculate_max_drawdown_abs(profits, relative, highd, lowdays, result, r
|
||||
"""
|
||||
init_date = datetime(2020, 1, 1, tzinfo=timezone.utc)
|
||||
dates = [init_date + timedelta(days=i) for i in range(len(profits))]
|
||||
df = DataFrame(zip(profits, dates), columns=["profit_abs", "open_date"])
|
||||
df = DataFrame(zip(profits, dates, strict=False), columns=["profit_abs", "open_date"])
|
||||
# sort by profit and reset index
|
||||
df = df.sort_values("profit_abs").reset_index(drop=True)
|
||||
df1 = df.copy()
|
||||
|
||||
@@ -416,7 +416,17 @@ def test_hdf5datahandler_ohlcv_load_and_resave(
|
||||
)
|
||||
@pytest.mark.parametrize("datahandler", ["hdf5", "feather", "parquet"])
|
||||
def test_generic_datahandler_ohlcv_load_and_resave(
|
||||
datahandler, testdatadir, tmp_path, pair, timeframe, candle_type, candle_append, startdt, enddt
|
||||
datahandler,
|
||||
mocker,
|
||||
testdatadir,
|
||||
tmp_path,
|
||||
pair,
|
||||
timeframe,
|
||||
candle_type,
|
||||
candle_append,
|
||||
startdt,
|
||||
enddt,
|
||||
caplog,
|
||||
):
|
||||
tmpdir2 = tmp_path
|
||||
if candle_type not in ("", "spot"):
|
||||
@@ -458,9 +468,26 @@ def test_generic_datahandler_ohlcv_load_and_resave(
|
||||
assert ohlcv[ohlcv["date"] > enddt].empty
|
||||
|
||||
# Try loading inexisting file
|
||||
ohlcv = dh.ohlcv_load("UNITTEST/NONEXIST", timeframe, candle_type=candle_type)
|
||||
ohlcv = dh1.ohlcv_load("UNITTEST/NONEXIST", timeframe, candle_type=candle_type)
|
||||
assert ohlcv.empty
|
||||
|
||||
# Try loading a file that exists but errors
|
||||
mocker.patch(
|
||||
"freqtrade.data.history.datahandlers.featherdatahandler.read_feather",
|
||||
side_effect=Exception("Test"),
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.data.history.datahandlers.parquetdatahandler.read_parquet",
|
||||
side_effect=Exception("Test"),
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.data.history.datahandlers.hdf5datahandler.pd.read_hdf",
|
||||
side_effect=Exception("Test"),
|
||||
)
|
||||
ohlcv_e = dh1.ohlcv_load("UNITTEST/NEW", timeframe, candle_type=candle_type)
|
||||
assert ohlcv_e.empty
|
||||
assert log_has_re("Error loading data from", caplog)
|
||||
|
||||
|
||||
def test_hdf5datahandler_ohlcv_purge(mocker, testdatadir):
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||
|
||||
@@ -6,7 +6,7 @@ from freqtrade.configuration.config_setup import setup_utils_configuration
|
||||
from freqtrade.data.history.history_utils import download_data_main
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from tests.conftest import EXMS, log_has, patch_exchange
|
||||
from tests.conftest import EXMS, log_has_re, patch_exchange
|
||||
|
||||
|
||||
def test_download_data_main_no_markets(mocker, caplog):
|
||||
@@ -19,8 +19,8 @@ def test_download_data_main_no_markets(mocker, caplog):
|
||||
config = setup_utils_configuration({"exchange": "binance"}, RunMode.UTIL_EXCHANGE)
|
||||
config.update({"days": 20, "pairs": ["ETH/BTC", "XRP/BTC"], "timeframes": ["5m", "1h"]})
|
||||
download_data_main(config)
|
||||
assert dl_mock.call_args[1]["timerange"].starttype == "date"
|
||||
assert log_has("Pairs [ETH/BTC,XRP/BTC] not available on exchange Binance.", caplog)
|
||||
assert dl_mock.call_count == 0
|
||||
assert log_has_re("No pairs available for download..*", caplog)
|
||||
|
||||
|
||||
def test_download_data_main_all_pairs(mocker, markets):
|
||||
@@ -55,7 +55,7 @@ def test_download_data_main_trades(mocker):
|
||||
"freqtrade.data.history.history_utils.convert_trades_to_ohlcv", MagicMock(return_value=[])
|
||||
)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch(f"{EXMS}.get_markets", return_value={})
|
||||
mocker.patch(f"{EXMS}.get_markets", return_value={"ETH/BTC": {}, "XRP/BTC": {}})
|
||||
config = setup_utils_configuration({"exchange": "binance"}, RunMode.UTIL_EXCHANGE)
|
||||
config.update(
|
||||
{
|
||||
@@ -91,7 +91,7 @@ def test_download_data_main_trades(mocker):
|
||||
|
||||
def test_download_data_main_data_invalid(mocker):
|
||||
patch_exchange(mocker, exchange="kraken")
|
||||
mocker.patch(f"{EXMS}.get_markets", return_value={})
|
||||
mocker.patch(f"{EXMS}.get_markets", return_value={"ETH/BTC": {}})
|
||||
config = setup_utils_configuration({"exchange": "kraken"}, RunMode.UTIL_EXCHANGE)
|
||||
config.update(
|
||||
{
|
||||
|
||||
+25
-23
@@ -210,7 +210,7 @@ def test_json_pair_trades_filename(pair, trading_mode, expected_result):
|
||||
assert fn == Path(expected_result + ".gz")
|
||||
|
||||
|
||||
def test_load_cached_data_for_updating(mocker, testdatadir) -> None:
|
||||
def test_load_cached_data_for_updating(testdatadir) -> None:
|
||||
data_handler = get_datahandler(testdatadir, "json")
|
||||
|
||||
test_data = None
|
||||
@@ -225,13 +225,14 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None:
|
||||
now_ts = test_data[-1][0] / 1000 + 60 * 60
|
||||
|
||||
# timeframe starts earlier than the cached data
|
||||
# should fully update data
|
||||
# Update timestamp to candle end date
|
||||
timerange = TimeRange("date", None, test_data[0][0] / 1000 - 1, 0)
|
||||
data, start_ts, end_ts = _load_cached_data_for_updating(
|
||||
"UNITTEST/BTC", "1m", timerange, data_handler, CandleType.SPOT
|
||||
)
|
||||
assert data.empty
|
||||
assert start_ts == test_data[0][0] - 1000
|
||||
assert not data.empty
|
||||
# Last candle was removed - so 1 candle overlap
|
||||
assert start_ts == test_data[-1][0] - 60 * 1000
|
||||
assert end_ts is None
|
||||
|
||||
# timeframe starts earlier than the cached data - prepending
|
||||
@@ -589,8 +590,8 @@ def test_download_data_no_markets(mocker, default_conf, caplog, testdatadir):
|
||||
)
|
||||
|
||||
assert dl_mock.call_count == 0
|
||||
assert "BTT/BTC" in unav_pairs
|
||||
assert "LTC/USDT" in unav_pairs
|
||||
assert "BTT/BTC: Pair not available on exchange." in unav_pairs
|
||||
assert "LTC/USDT: Pair not available on exchange." in unav_pairs
|
||||
assert log_has("Skipping pair BTT/BTC...", caplog)
|
||||
|
||||
|
||||
@@ -617,7 +618,7 @@ def test_refresh_backtest_trades_data(mocker, default_conf, markets, caplog, tes
|
||||
assert dl_mock.call_args[1]["timerange"].starttype == "date"
|
||||
|
||||
assert log_has("Downloading trades for pair ETH/BTC.", caplog)
|
||||
assert unavailable_pairs == ["XRP/ETH"]
|
||||
assert [p for p in unavailable_pairs if "XRP/ETH" in p]
|
||||
assert log_has("Skipping pair XRP/ETH...", caplog)
|
||||
|
||||
|
||||
@@ -665,13 +666,16 @@ def test_download_trades_history(
|
||||
|
||||
file1.unlink()
|
||||
|
||||
mocker.patch(f"{EXMS}.get_historic_trades", MagicMock(side_effect=ValueError))
|
||||
mocker.patch(f"{EXMS}.get_historic_trades", MagicMock(side_effect=ValueError("he ho!")))
|
||||
caplog.clear()
|
||||
|
||||
assert not _download_trades_history(
|
||||
data_handler=data_handler, exchange=exchange, pair="ETH/BTC", trading_mode=TradingMode.SPOT
|
||||
)
|
||||
assert log_has_re('Failed to download and store historic trades for pair: "ETH/BTC".*', caplog)
|
||||
with pytest.raises(ValueError, match="he ho!"):
|
||||
_download_trades_history(
|
||||
data_handler=data_handler,
|
||||
exchange=exchange,
|
||||
pair="ETH/BTC",
|
||||
trading_mode=TradingMode.SPOT,
|
||||
)
|
||||
|
||||
file2 = tmp_path / "XRP_ETH-trades.json.gz"
|
||||
copyfile(testdatadir / file2.name, file2)
|
||||
@@ -682,17 +686,15 @@ def test_download_trades_history(
|
||||
since_time = int(trades_history[0][0] // 1000) - 500
|
||||
timerange = TimeRange("date", None, since_time, 0)
|
||||
|
||||
assert _download_trades_history(
|
||||
data_handler=data_handler,
|
||||
exchange=exchange,
|
||||
pair="XRP/ETH",
|
||||
timerange=timerange,
|
||||
trading_mode=TradingMode.SPOT,
|
||||
)
|
||||
with pytest.raises(ValueError, match=r"Start .* earlier than available data"):
|
||||
_download_trades_history(
|
||||
data_handler=data_handler,
|
||||
exchange=exchange,
|
||||
pair="XRP/ETH",
|
||||
timerange=timerange,
|
||||
trading_mode=TradingMode.SPOT,
|
||||
)
|
||||
|
||||
assert ght_mock.call_count == 1
|
||||
assert ght_mock.call_count == 0
|
||||
|
||||
assert int(ght_mock.call_args_list[0][1]["since"] // 1000) == since_time
|
||||
assert ght_mock.call_args_list[0][1]["from_id"] is None
|
||||
assert log_has_re(r"Start .* earlier than available data. Redownloading trades for.*", caplog)
|
||||
_clean_test_file(file2)
|
||||
|
||||
@@ -794,3 +794,57 @@ def test_get_maintenance_ratio_and_amt_binance(
|
||||
exchange._leverage_tiers = leverage_tiers
|
||||
(result_ratio, result_amt) = exchange.get_maintenance_ratio_and_amt(pair, notional_value)
|
||||
assert (round(result_ratio, 8), round(result_amt, 8)) == (mm_ratio, amt)
|
||||
|
||||
|
||||
async def test__async_get_trade_history_id_binance(default_conf_usdt, mocker, fetch_trades_result):
|
||||
exchange = get_patched_exchange(mocker, default_conf_usdt, exchange="binance")
|
||||
|
||||
async def mock_get_trade_hist(pair, *args, **kwargs):
|
||||
if "since" in kwargs:
|
||||
# older than initial call
|
||||
if kwargs["since"] < 1565798399752:
|
||||
return []
|
||||
else:
|
||||
# Don't expect to get here
|
||||
raise ValueError("Unexpected call")
|
||||
# return fetch_trades_result[:-2]
|
||||
elif kwargs.get("params", {}).get(exchange._trades_pagination_arg) == "0":
|
||||
# Return first 3
|
||||
return fetch_trades_result[:-2]
|
||||
elif kwargs.get("params", {}).get(exchange._trades_pagination_arg) in (
|
||||
fetch_trades_result[-3]["id"],
|
||||
1565798399752,
|
||||
):
|
||||
# Return 2
|
||||
return fetch_trades_result[-3:-1]
|
||||
else:
|
||||
# Return last 2
|
||||
return fetch_trades_result[-2:]
|
||||
|
||||
exchange._api_async.fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
|
||||
|
||||
pair = "ETH/BTC"
|
||||
ret = await exchange._async_get_trade_history_id(
|
||||
pair,
|
||||
since=fetch_trades_result[0]["timestamp"],
|
||||
until=fetch_trades_result[-1]["timestamp"] - 1,
|
||||
)
|
||||
assert ret[0] == pair
|
||||
assert isinstance(ret[1], list)
|
||||
assert exchange._api_async.fetch_trades.call_count == 4
|
||||
|
||||
fetch_trades_cal = exchange._api_async.fetch_trades.call_args_list
|
||||
# first call (using since, not fromId)
|
||||
assert fetch_trades_cal[0][0][0] == pair
|
||||
assert fetch_trades_cal[0][1]["since"] == fetch_trades_result[0]["timestamp"]
|
||||
|
||||
# 2nd call
|
||||
assert fetch_trades_cal[1][0][0] == pair
|
||||
assert "params" in fetch_trades_cal[1][1]
|
||||
pagination_arg = exchange._ft_has["trades_pagination_arg"]
|
||||
assert pagination_arg in fetch_trades_cal[1][1]["params"]
|
||||
# Initial call was with from_id = "0"
|
||||
assert fetch_trades_cal[1][1]["params"][pagination_arg] == "0"
|
||||
|
||||
assert fetch_trades_cal[2][1]["params"][pagination_arg] != "0"
|
||||
assert fetch_trades_cal[3][1]["params"][pagination_arg] != "0"
|
||||
|
||||
@@ -1,6 +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 tests.conftest import EXMS, get_mock_coro, get_patched_exchange, log_has
|
||||
@@ -172,3 +174,26 @@ def test_bybit_fetch_order_canceled_empty(default_conf_usdt, mocker):
|
||||
assert res2["filled"] == 0.0
|
||||
assert res2["amount"] == 20.0
|
||||
assert res2["status"] == "open"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"side,order_type,uta,tradingmode,expected",
|
||||
[
|
||||
("buy", "limit", False, "spot", True),
|
||||
("buy", "limit", False, "futures", True),
|
||||
("sell", "limit", False, "spot", True),
|
||||
("sell", "limit", False, "futures", True),
|
||||
("buy", "market", False, "spot", True),
|
||||
("buy", "market", False, "futures", False),
|
||||
("buy", "market", True, "spot", False),
|
||||
("buy", "market", True, "futures", False),
|
||||
],
|
||||
)
|
||||
def test_bybit__order_needs_price(
|
||||
default_conf, mocker, side, order_type, uta, tradingmode, expected
|
||||
):
|
||||
exchange = get_patched_exchange(mocker, default_conf, exchange="bybit")
|
||||
exchange.trading_mode = tradingmode
|
||||
exchange.unified_account = uta
|
||||
|
||||
assert exchange._order_needs_price(side, order_type) == expected
|
||||
|
||||
@@ -1069,7 +1069,6 @@ def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverag
|
||||
assert order["type"] == "limit"
|
||||
assert order["symbol"] == "ETH/BTC"
|
||||
assert order["amount"] == 1
|
||||
assert order["leverage"] == leverage
|
||||
assert order["cost"] == 1 * 200
|
||||
|
||||
|
||||
@@ -1274,6 +1273,9 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
|
||||
exchange._set_leverage = MagicMock()
|
||||
exchange.set_margin_mode = MagicMock()
|
||||
|
||||
# Only applies to gate
|
||||
price_req = exchange._ft_has.get("marketOrderRequiresPrice", False)
|
||||
|
||||
order = exchange.create_order(
|
||||
pair="XLTCUSDT", ordertype=ordertype, side=side, amount=1, rate=rate, leverage=1.0
|
||||
)
|
||||
@@ -1286,7 +1288,9 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
|
||||
assert api_mock.create_order.call_args[0][1] == ordertype
|
||||
assert api_mock.create_order.call_args[0][2] == side
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is rate
|
||||
assert api_mock.create_order.call_args[0][4] == (
|
||||
rate if price_req or not (bool(marketprice) and side == "sell") else None
|
||||
)
|
||||
assert exchange._set_leverage.call_count == 0
|
||||
assert exchange.set_margin_mode.call_count == 0
|
||||
|
||||
@@ -1364,7 +1368,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == "buy"
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
if exchange._order_needs_price(order_type):
|
||||
if exchange._order_needs_price("buy", order_type):
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
else:
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
@@ -1511,7 +1515,7 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == "buy"
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
if exchange._order_needs_price(order_type):
|
||||
if exchange._order_needs_price("buy", order_type):
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
else:
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
@@ -1556,7 +1560,7 @@ def test_sell_prod(default_conf, mocker, exchange_name):
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == "sell"
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
if exchange._order_needs_price(order_type):
|
||||
if exchange._order_needs_price("sell", order_type):
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
else:
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
@@ -1666,7 +1670,7 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == "sell"
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
if exchange._order_needs_price(order_type):
|
||||
if exchange._order_needs_price("sell", order_type):
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
else:
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
@@ -4446,6 +4450,7 @@ def test_market_is_tradable(
|
||||
ex = get_patched_exchange(mocker, default_conf, exchange=exchange)
|
||||
market = {
|
||||
"symbol": market_symbol,
|
||||
"type": "swap",
|
||||
"base": base,
|
||||
"quote": quote,
|
||||
"spot": spot,
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.conftest import EXMS, get_mock_coro, get_patched_exchange
|
||||
|
||||
|
||||
def test_hyperliquid_dry_run_liquidation_price(default_conf, mocker):
|
||||
# test if liq price calculated by dry_run_liquidation_price() is close to ccxt liq price
|
||||
# testing different pairs with large/small prices, different leverages, long, short
|
||||
markets = {
|
||||
"BTC/USDC:USDC": {"limits": {"leverage": {"max": 50}}},
|
||||
"ETH/USDC:USDC": {"limits": {"leverage": {"max": 50}}},
|
||||
"SOL/USDC:USDC": {"limits": {"leverage": {"max": 20}}},
|
||||
"DOGE/USDC:USDC": {"limits": {"leverage": {"max": 20}}},
|
||||
}
|
||||
positions = [
|
||||
{
|
||||
"symbol": "ETH/USDC:USDC",
|
||||
"entryPrice": 2458.5,
|
||||
"side": "long",
|
||||
"contracts": 0.015,
|
||||
"collateral": 36.864593,
|
||||
"leverage": 1.0,
|
||||
"liquidationPrice": 0.86915825,
|
||||
},
|
||||
{
|
||||
"symbol": "BTC/USDC:USDC",
|
||||
"entryPrice": 63287.0,
|
||||
"side": "long",
|
||||
"contracts": 0.00039,
|
||||
"collateral": 24.673292,
|
||||
"leverage": 1.0,
|
||||
"liquidationPrice": 22.37166537,
|
||||
},
|
||||
{
|
||||
"symbol": "SOL/USDC:USDC",
|
||||
"entryPrice": 146.82,
|
||||
"side": "long",
|
||||
"contracts": 0.16,
|
||||
"collateral": 23.482979,
|
||||
"leverage": 1.0,
|
||||
"liquidationPrice": 0.05269872,
|
||||
},
|
||||
{
|
||||
"symbol": "SOL/USDC:USDC",
|
||||
"entryPrice": 145.83,
|
||||
"side": "long",
|
||||
"contracts": 0.33,
|
||||
"collateral": 24.045107,
|
||||
"leverage": 2.0,
|
||||
"liquidationPrice": 74.83696193,
|
||||
},
|
||||
{
|
||||
"symbol": "ETH/USDC:USDC",
|
||||
"entryPrice": 2459.5,
|
||||
"side": "long",
|
||||
"contracts": 0.0199,
|
||||
"collateral": 24.454895,
|
||||
"leverage": 2.0,
|
||||
"liquidationPrice": 1243.0411908,
|
||||
},
|
||||
{
|
||||
"symbol": "BTC/USDC:USDC",
|
||||
"entryPrice": 62739.0,
|
||||
"side": "long",
|
||||
"contracts": 0.00077,
|
||||
"collateral": 24.137992,
|
||||
"leverage": 2.0,
|
||||
"liquidationPrice": 31708.03843631,
|
||||
},
|
||||
{
|
||||
"symbol": "DOGE/USDC:USDC",
|
||||
"entryPrice": 0.11586,
|
||||
"side": "long",
|
||||
"contracts": 437.0,
|
||||
"collateral": 25.29769,
|
||||
"leverage": 2.0,
|
||||
"liquidationPrice": 0.05945697,
|
||||
},
|
||||
{
|
||||
"symbol": "ETH/USDC:USDC",
|
||||
"entryPrice": 2642.8,
|
||||
"side": "short",
|
||||
"contracts": 0.019,
|
||||
"collateral": 25.091876,
|
||||
"leverage": 2.0,
|
||||
"liquidationPrice": 3924.18322043,
|
||||
},
|
||||
{
|
||||
"symbol": "SOL/USDC:USDC",
|
||||
"entryPrice": 155.89,
|
||||
"side": "short",
|
||||
"contracts": 0.32,
|
||||
"collateral": 24.924941,
|
||||
"leverage": 2.0,
|
||||
"liquidationPrice": 228.07847866,
|
||||
},
|
||||
{
|
||||
"symbol": "DOGE/USDC:USDC",
|
||||
"entryPrice": 0.14333,
|
||||
"side": "short",
|
||||
"contracts": 351.0,
|
||||
"collateral": 25.136807,
|
||||
"leverage": 2.0,
|
||||
"liquidationPrice": 0.20970228,
|
||||
},
|
||||
{
|
||||
"symbol": "BTC/USDC:USDC",
|
||||
"entryPrice": 68595.0,
|
||||
"side": "short",
|
||||
"contracts": 0.00069,
|
||||
"collateral": 23.64871,
|
||||
"leverage": 2.0,
|
||||
"liquidationPrice": 101849.99354283,
|
||||
},
|
||||
{
|
||||
"symbol": "BTC/USDC:USDC",
|
||||
"entryPrice": 65536.0,
|
||||
"side": "short",
|
||||
"contracts": 0.00099,
|
||||
"collateral": 21.604172,
|
||||
"leverage": 3.0,
|
||||
"liquidationPrice": 86493.46174617,
|
||||
},
|
||||
{
|
||||
"symbol": "SOL/USDC:USDC",
|
||||
"entryPrice": 173.06,
|
||||
"side": "long",
|
||||
"contracts": 0.6,
|
||||
"collateral": 20.735658,
|
||||
"leverage": 5.0,
|
||||
"liquidationPrice": 142.05186667,
|
||||
},
|
||||
{
|
||||
"symbol": "ETH/USDC:USDC",
|
||||
"entryPrice": 2545.5,
|
||||
"side": "long",
|
||||
"contracts": 0.0329,
|
||||
"collateral": 20.909894,
|
||||
"leverage": 4.0,
|
||||
"liquidationPrice": 1929.23322895,
|
||||
},
|
||||
{
|
||||
"symbol": "BTC/USDC:USDC",
|
||||
"entryPrice": 67400.0,
|
||||
"side": "short",
|
||||
"contracts": 0.00031,
|
||||
"collateral": 20.887308,
|
||||
"leverage": 1.0,
|
||||
"liquidationPrice": 133443.97317151,
|
||||
},
|
||||
{
|
||||
"symbol": "ETH/USDC:USDC",
|
||||
"entryPrice": 2552.0,
|
||||
"side": "short",
|
||||
"contracts": 0.0327,
|
||||
"collateral": 20.833393,
|
||||
"leverage": 4.0,
|
||||
"liquidationPrice": 3157.53150453,
|
||||
},
|
||||
{
|
||||
"symbol": "BTC/USDC:USDC",
|
||||
"entryPrice": 66930.0,
|
||||
"side": "long",
|
||||
"contracts": 0.0015,
|
||||
"collateral": 20.043862,
|
||||
"leverage": 5.0,
|
||||
"liquidationPrice": 54108.51043771,
|
||||
},
|
||||
{
|
||||
"symbol": "BTC/USDC:USDC",
|
||||
"entryPrice": 67033.0,
|
||||
"side": "long",
|
||||
"contracts": 0.00121,
|
||||
"collateral": 20.251817,
|
||||
"leverage": 4.0,
|
||||
"liquidationPrice": 50804.00091827,
|
||||
},
|
||||
{
|
||||
"symbol": "ETH/USDC:USDC",
|
||||
"entryPrice": 2521.9,
|
||||
"side": "long",
|
||||
"contracts": 0.0237,
|
||||
"collateral": 19.902091,
|
||||
"leverage": 3.0,
|
||||
"liquidationPrice": 1699.14071943,
|
||||
},
|
||||
{
|
||||
"symbol": "BTC/USDC:USDC",
|
||||
"entryPrice": 68139.0,
|
||||
"side": "short",
|
||||
"contracts": 0.00145,
|
||||
"collateral": 19.72573,
|
||||
"leverage": 5.0,
|
||||
"liquidationPrice": 80933.61590987,
|
||||
},
|
||||
{
|
||||
"symbol": "SOL/USDC:USDC",
|
||||
"entryPrice": 178.29,
|
||||
"side": "short",
|
||||
"contracts": 0.11,
|
||||
"collateral": 19.605036,
|
||||
"leverage": 1.0,
|
||||
"liquidationPrice": 347.82205322,
|
||||
},
|
||||
{
|
||||
"symbol": "SOL/USDC:USDC",
|
||||
"entryPrice": 176.23,
|
||||
"side": "long",
|
||||
"contracts": 0.33,
|
||||
"collateral": 19.364946,
|
||||
"leverage": 3.0,
|
||||
"liquidationPrice": 120.56240404,
|
||||
},
|
||||
{
|
||||
"symbol": "SOL/USDC:USDC",
|
||||
"entryPrice": 173.08,
|
||||
"side": "short",
|
||||
"contracts": 0.33,
|
||||
"collateral": 19.01881,
|
||||
"leverage": 3.0,
|
||||
"liquidationPrice": 225.08561715,
|
||||
},
|
||||
{
|
||||
"symbol": "BTC/USDC:USDC",
|
||||
"entryPrice": 68240.0,
|
||||
"side": "short",
|
||||
"contracts": 0.00105,
|
||||
"collateral": 17.887922,
|
||||
"leverage": 4.0,
|
||||
"liquidationPrice": 84431.79820839,
|
||||
},
|
||||
{
|
||||
"symbol": "ETH/USDC:USDC",
|
||||
"entryPrice": 2518.4,
|
||||
"side": "short",
|
||||
"contracts": 0.007,
|
||||
"collateral": 17.62263,
|
||||
"leverage": 1.0,
|
||||
"liquidationPrice": 4986.05799151,
|
||||
},
|
||||
{
|
||||
"symbol": "ETH/USDC:USDC",
|
||||
"entryPrice": 2533.2,
|
||||
"side": "long",
|
||||
"contracts": 0.0347,
|
||||
"collateral": 17.555195,
|
||||
"leverage": 5.0,
|
||||
"liquidationPrice": 2047.7642302,
|
||||
},
|
||||
{
|
||||
"symbol": "DOGE/USDC:USDC",
|
||||
"entryPrice": 0.13284,
|
||||
"side": "long",
|
||||
"contracts": 360.0,
|
||||
"collateral": 15.943218,
|
||||
"leverage": 3.0,
|
||||
"liquidationPrice": 0.09082388,
|
||||
},
|
||||
{
|
||||
"symbol": "SOL/USDC:USDC",
|
||||
"entryPrice": 163.11,
|
||||
"side": "short",
|
||||
"contracts": 0.48,
|
||||
"collateral": 15.650731,
|
||||
"leverage": 5.0,
|
||||
"liquidationPrice": 190.94213618,
|
||||
},
|
||||
{
|
||||
"symbol": "BTC/USDC:USDC",
|
||||
"entryPrice": 67141.0,
|
||||
"side": "long",
|
||||
"contracts": 0.00067,
|
||||
"collateral": 14.979079,
|
||||
"leverage": 3.0,
|
||||
"liquidationPrice": 45236.52992613,
|
||||
},
|
||||
]
|
||||
|
||||
api_mock = MagicMock()
|
||||
default_conf["trading_mode"] = "futures"
|
||||
default_conf["margin_mode"] = "isolated"
|
||||
default_conf["stake_currency"] = "USDC"
|
||||
api_mock.load_markets = get_mock_coro(return_value=markets)
|
||||
exchange = get_patched_exchange(
|
||||
mocker, default_conf, api_mock, exchange="hyperliquid", mock_markets=False
|
||||
)
|
||||
|
||||
for position in positions:
|
||||
is_short = True if position["side"] == "short" else False
|
||||
liq_price_returned = position["liquidationPrice"]
|
||||
liq_price_calculated = exchange.dry_run_liquidation_price(
|
||||
position["symbol"],
|
||||
position["entryPrice"],
|
||||
is_short,
|
||||
position["contracts"],
|
||||
position["collateral"],
|
||||
position["leverage"],
|
||||
position["collateral"],
|
||||
[],
|
||||
)
|
||||
assert pytest.approx(liq_price_returned, rel=0.0001) == liq_price_calculated
|
||||
|
||||
|
||||
def test_hyperliquid_get_funding_fees(default_conf, mocker):
|
||||
now = datetime.now(timezone.utc)
|
||||
exchange = get_patched_exchange(mocker, default_conf, exchange="hyperliquid")
|
||||
exchange._fetch_and_calculate_funding_fees = MagicMock()
|
||||
exchange.get_funding_fees("BTC/USDC:USDC", 1, False, now)
|
||||
assert exchange._fetch_and_calculate_funding_fees.call_count == 0
|
||||
|
||||
default_conf["trading_mode"] = "futures"
|
||||
default_conf["margin_mode"] = "isolated"
|
||||
exchange = get_patched_exchange(mocker, default_conf, exchange="hyperliquid")
|
||||
exchange._fetch_and_calculate_funding_fees = MagicMock()
|
||||
exchange.get_funding_fees("BTC/USDC:USDC", 1, False, now)
|
||||
|
||||
assert exchange._fetch_and_calculate_funding_fees.call_count == 1
|
||||
|
||||
|
||||
def test_hyperliquid_get_max_leverage(default_conf, mocker):
|
||||
markets = {
|
||||
"BTC/USDC:USDC": {"limits": {"leverage": {"max": 50}}},
|
||||
"ETH/USDC:USDC": {"limits": {"leverage": {"max": 50}}},
|
||||
"SOL/USDC:USDC": {"limits": {"leverage": {"max": 20}}},
|
||||
"DOGE/USDC:USDC": {"limits": {"leverage": {"max": 20}}},
|
||||
}
|
||||
exchange = get_patched_exchange(mocker, default_conf, exchange="hyperliquid")
|
||||
assert exchange.get_max_leverage("BTC/USDC:USDC", 1) == 1.0
|
||||
|
||||
default_conf["trading_mode"] = "futures"
|
||||
default_conf["margin_mode"] = "isolated"
|
||||
exchange = get_patched_exchange(mocker, default_conf, exchange="hyperliquid")
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
markets=PropertyMock(return_value=markets),
|
||||
)
|
||||
|
||||
assert exchange.get_max_leverage("BTC/USDC:USDC", 1) == 50
|
||||
assert exchange.get_max_leverage("ETH/USDC:USDC", 20) == 50
|
||||
assert exchange.get_max_leverage("SOL/USDC:USDC", 50) == 20
|
||||
assert exchange.get_max_leverage("DOGE/USDC:USDC", 3) == 20
|
||||
|
||||
|
||||
def test_hyperliquid__lev_prep(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
api_mock.set_margin_mode = MagicMock()
|
||||
type(api_mock).has = PropertyMock(return_value={"setMarginMode": True})
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="hyperliquid")
|
||||
exchange._lev_prep("BTC/USDC:USDC", 3.2, "buy")
|
||||
|
||||
assert api_mock.set_margin_mode.call_count == 0
|
||||
|
||||
# test in futures mode
|
||||
api_mock.set_margin_mode.reset_mock()
|
||||
default_conf["dry_run"] = False
|
||||
|
||||
default_conf["trading_mode"] = "futures"
|
||||
default_conf["margin_mode"] = "isolated"
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="hyperliquid")
|
||||
exchange._lev_prep("BTC/USDC:USDC", 3.2, "buy")
|
||||
|
||||
assert api_mock.set_margin_mode.call_count == 1
|
||||
api_mock.set_margin_mode.assert_called_with("isolated", "BTC/USDC:USDC", {"leverage": 3})
|
||||
|
||||
api_mock.reset_mock()
|
||||
|
||||
exchange._lev_prep("BTC/USDC:USDC", 19.99, "sell")
|
||||
|
||||
assert api_mock.set_margin_mode.call_count == 1
|
||||
api_mock.set_margin_mode.assert_called_with("isolated", "BTC/USDC:USDC", {"leverage": 19})
|
||||
|
||||
|
||||
def test_hyperliquid_fetch_order(default_conf_usdt, mocker):
|
||||
default_conf_usdt["dry_run"] = False
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_order = MagicMock(
|
||||
return_value={
|
||||
"id": "12345",
|
||||
"symbol": "ETH/USDC:USDC",
|
||||
"status": "closed",
|
||||
"filled": 0.1,
|
||||
"average": None,
|
||||
"timestamp": 1630000000,
|
||||
}
|
||||
)
|
||||
|
||||
mocker.patch(f"{EXMS}.exchange_has", return_value=True)
|
||||
gtfo_mock = mocker.patch(
|
||||
f"{EXMS}.get_trades_for_order",
|
||||
return_value=[
|
||||
{
|
||||
"order_id": "12345",
|
||||
"price": 1000,
|
||||
"amount": 3,
|
||||
"filled": 3,
|
||||
"remaining": 0,
|
||||
},
|
||||
{
|
||||
"order_id": "12345",
|
||||
"price": 3000,
|
||||
"amount": 1,
|
||||
"filled": 1,
|
||||
"remaining": 0,
|
||||
},
|
||||
],
|
||||
)
|
||||
exchange = get_patched_exchange(mocker, default_conf_usdt, api_mock, exchange="hyperliquid")
|
||||
o = exchange.fetch_order("12345", "ETH/USDC:USDC")
|
||||
# Uses weighted average
|
||||
assert o["average"] == 1500
|
||||
|
||||
assert gtfo_mock.call_count == 1
|
||||
@@ -56,13 +56,14 @@ def test_kraken_trading_agreement(default_conf, mocker, order_type, time_in_forc
|
||||
assert api_mock.create_order.call_args[0][5] == expected_params
|
||||
|
||||
|
||||
def test_get_balances_prod(default_conf, mocker):
|
||||
balance_item = {"free": None, "total": 10.0, "used": 0.0}
|
||||
def test_get_balances_prod_kraken(default_conf, mocker):
|
||||
balance_item = {"free": 0.0, "total": 10.0, "used": 0.0}
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_balance = MagicMock(
|
||||
return_value={
|
||||
"1ST": balance_item.copy(),
|
||||
"1ST": {"free": 0.0, "total": 0.0, "used": 0.0},
|
||||
"1ST.F": balance_item.copy(), # When "rewards" is enabled, the balance is in ".F"
|
||||
"2ND": balance_item.copy(),
|
||||
"3RD": balance_item.copy(),
|
||||
"4TH": balance_item.copy(),
|
||||
|
||||
+313
-176
@@ -30,40 +30,64 @@ EXCHANGES = {
|
||||
"private_methods": ["fapiPrivateGetPositionSideDual", "fapiPrivateGetMultiAssetsMargin"],
|
||||
"sample_order": [
|
||||
{
|
||||
"symbol": "SOLUSDT",
|
||||
"orderId": 3551312894,
|
||||
"orderListId": -1,
|
||||
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
|
||||
"transactTime": 1674493798550,
|
||||
"price": "15.50000000",
|
||||
"origQty": "1.10000000",
|
||||
"executedQty": "0.00000000",
|
||||
"cummulativeQuoteQty": "0.00000000",
|
||||
"status": "NEW",
|
||||
"timeInForce": "GTC",
|
||||
"type": "LIMIT",
|
||||
"side": "BUY",
|
||||
"workingTime": 1674493798550,
|
||||
"fills": [],
|
||||
"selfTradePreventionMode": "NONE",
|
||||
"exchange_response": {
|
||||
"symbol": "SOLUSDT",
|
||||
"orderId": 3551312894,
|
||||
"orderListId": -1,
|
||||
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
|
||||
"transactTime": 1674493798550,
|
||||
"price": "15.50000000",
|
||||
"origQty": "1.10000000",
|
||||
"executedQty": "0.00000000",
|
||||
"cummulativeQuoteQty": "0.00000000",
|
||||
"status": "NEW",
|
||||
"timeInForce": "GTC",
|
||||
"type": "LIMIT",
|
||||
"side": "BUY",
|
||||
"workingTime": 1674493798550,
|
||||
"fills": [],
|
||||
"selfTradePreventionMode": "NONE",
|
||||
},
|
||||
"pair": "SOL/USDT",
|
||||
"expected": {
|
||||
"symbol": "SOL/USDT",
|
||||
"orderId": "3551312894",
|
||||
"timestamp": 1674493798550,
|
||||
"datetime": "2023-03-25T15:49:58.550Z",
|
||||
"price": 15.5,
|
||||
"status": "open",
|
||||
"amount": 1.1,
|
||||
},
|
||||
},
|
||||
{
|
||||
"symbol": "SOLUSDT",
|
||||
"orderId": 3551312894,
|
||||
"orderListId": -1,
|
||||
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
|
||||
"transactTime": 1674493798550,
|
||||
"price": "15.50000000",
|
||||
"origQty": "1.10000000",
|
||||
"executedQty": "1.10000000",
|
||||
"cummulativeQuoteQty": "17.05",
|
||||
"status": "FILLED",
|
||||
"timeInForce": "GTC",
|
||||
"type": "LIMIT",
|
||||
"side": "BUY",
|
||||
"workingTime": 1674493798550,
|
||||
"fills": [],
|
||||
"selfTradePreventionMode": "NONE",
|
||||
"exchange_response": {
|
||||
"symbol": "SOLUSDT",
|
||||
"orderId": 3551312894,
|
||||
"orderListId": -1,
|
||||
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
|
||||
"transactTime": 1674493798550,
|
||||
"price": "15.50000000",
|
||||
"origQty": "1.10000000",
|
||||
"executedQty": "1.10000000",
|
||||
"cummulativeQuoteQty": "17.05",
|
||||
"status": "FILLED",
|
||||
"timeInForce": "GTC",
|
||||
"type": "LIMIT",
|
||||
"side": "BUY",
|
||||
"workingTime": 1674493798550,
|
||||
"fills": [],
|
||||
"selfTradePreventionMode": "NONE",
|
||||
},
|
||||
"pair": "SOL/USDT",
|
||||
"expected": {
|
||||
"symbol": "SOL/USDT",
|
||||
"orderId": "3551312894",
|
||||
"timestamp": 1674493798550,
|
||||
"datetime": "2023-03-25T15:49:58.550Z",
|
||||
"price": 15.5,
|
||||
"status": "open",
|
||||
"amount": 1.1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -73,24 +97,37 @@ EXCHANGES = {
|
||||
"hasQuoteVolume": True,
|
||||
"timeframe": "1h",
|
||||
"futures": False,
|
||||
"skip_ws_tests": True,
|
||||
"sample_order": [
|
||||
{
|
||||
"symbol": "SOLUSDT",
|
||||
"orderId": 3551312894,
|
||||
"orderListId": -1,
|
||||
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
|
||||
"transactTime": 1674493798550,
|
||||
"price": "15.50000000",
|
||||
"origQty": "1.10000000",
|
||||
"executedQty": "0.00000000",
|
||||
"cummulativeQuoteQty": "0.00000000",
|
||||
"status": "NEW",
|
||||
"timeInForce": "GTC",
|
||||
"type": "LIMIT",
|
||||
"side": "BUY",
|
||||
"workingTime": 1674493798550,
|
||||
"fills": [],
|
||||
"selfTradePreventionMode": "NONE",
|
||||
"exchange_response": {
|
||||
"symbol": "SOLUSDT",
|
||||
"orderId": 3551312894,
|
||||
"orderListId": -1,
|
||||
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
|
||||
"transactTime": 1674493798550,
|
||||
"price": "15.50000000",
|
||||
"origQty": "1.10000000",
|
||||
"executedQty": "0.00000000",
|
||||
"cummulativeQuoteQty": "0.00000000",
|
||||
"status": "NEW",
|
||||
"timeInForce": "GTC",
|
||||
"type": "LIMIT",
|
||||
"side": "BUY",
|
||||
"workingTime": 1674493798550,
|
||||
"fills": [],
|
||||
"selfTradePreventionMode": "NONE",
|
||||
},
|
||||
"pair": "SOL/USDT",
|
||||
"expected": {
|
||||
"symbol": "SOL/USDT",
|
||||
"orderId": "3551312894",
|
||||
"timestamp": 1674493798550,
|
||||
"datetime": "2023-03-25T15:49:58.550Z",
|
||||
"price": 15.5,
|
||||
"status": "open",
|
||||
"amount": 1.1,
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
@@ -111,38 +148,62 @@ EXCHANGES = {
|
||||
"leverage_tiers_public": False,
|
||||
"leverage_in_spot_market": True,
|
||||
"sample_order": [
|
||||
{"id": "63d6742d0adc5570001d2bbf7"}, # create order
|
||||
{
|
||||
"id": "63d6742d0adc5570001d2bbf7",
|
||||
"symbol": "SOL-USDT",
|
||||
"opType": "DEAL",
|
||||
"type": "limit",
|
||||
"side": "buy",
|
||||
"price": "15.5",
|
||||
"size": "1.1",
|
||||
"funds": "0",
|
||||
"dealFunds": "17.05",
|
||||
"dealSize": "1.1",
|
||||
"fee": "0.000065252",
|
||||
"feeCurrency": "USDT",
|
||||
"stp": "",
|
||||
"stop": "",
|
||||
"stopTriggered": False,
|
||||
"stopPrice": "0",
|
||||
"timeInForce": "GTC",
|
||||
"postOnly": False,
|
||||
"hidden": False,
|
||||
"iceberg": False,
|
||||
"visibleSize": "0",
|
||||
"cancelAfter": 0,
|
||||
"channel": "API",
|
||||
"clientOid": "0a053870-11bf-41e5-be61-b272a4cb62e1",
|
||||
"remark": None,
|
||||
"tags": "partner:ccxt",
|
||||
"isActive": False,
|
||||
"cancelExist": False,
|
||||
"createdAt": 1674493798550,
|
||||
"tradeType": "TRADE",
|
||||
"exchange_response": {"id": "63d6742d0adc5570001d2bbf7"},
|
||||
"pair": "SOL/USDT",
|
||||
"expected": {
|
||||
"symbol": "SOL/USDT",
|
||||
"orderId": "3551312894",
|
||||
"timestamp": 1674493798550,
|
||||
"datetime": "2023-03-25T15:49:58.550Z",
|
||||
"price": 15.5,
|
||||
"status": "open",
|
||||
"amount": 1.1,
|
||||
},
|
||||
}, # create order
|
||||
{
|
||||
"exchange_response": {
|
||||
"id": "63d6742d0adc5570001d2bbf7",
|
||||
"symbol": "SOL-USDT",
|
||||
"opType": "DEAL",
|
||||
"type": "limit",
|
||||
"side": "buy",
|
||||
"price": "15.5",
|
||||
"size": "1.1",
|
||||
"funds": "0",
|
||||
"dealFunds": "17.05",
|
||||
"dealSize": "1.1",
|
||||
"fee": "0.000065252",
|
||||
"feeCurrency": "USDT",
|
||||
"stp": "",
|
||||
"stop": "",
|
||||
"stopTriggered": False,
|
||||
"stopPrice": "0",
|
||||
"timeInForce": "GTC",
|
||||
"postOnly": False,
|
||||
"hidden": False,
|
||||
"iceberg": False,
|
||||
"visibleSize": "0",
|
||||
"cancelAfter": 0,
|
||||
"channel": "API",
|
||||
"clientOid": "0a053870-11bf-41e5-be61-b272a4cb62e1",
|
||||
"remark": None,
|
||||
"tags": "partner:ccxt",
|
||||
"isActive": False,
|
||||
"cancelExist": False,
|
||||
"createdAt": 1674493798550,
|
||||
"tradeType": "TRADE",
|
||||
},
|
||||
"pair": "SOL/USDT",
|
||||
"expected": {
|
||||
"symbol": "SOL/USDT",
|
||||
"orderId": "3551312894",
|
||||
"timestamp": 1674493798550,
|
||||
"datetime": "2023-03-25T15:49:58.550Z",
|
||||
"price": 15.5,
|
||||
"status": "open",
|
||||
"amount": 1.1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -158,65 +219,89 @@ EXCHANGES = {
|
||||
"leverage_in_spot_market": True,
|
||||
"sample_order": [
|
||||
{
|
||||
"id": "276266139423",
|
||||
"text": "apiv4",
|
||||
"create_time": "1674493798",
|
||||
"update_time": "1674493798",
|
||||
"create_time_ms": "1674493798550",
|
||||
"update_time_ms": "1674493798550",
|
||||
"status": "closed",
|
||||
"currency_pair": "SOL_USDT",
|
||||
"type": "limit",
|
||||
"account": "spot",
|
||||
"side": "buy",
|
||||
"amount": "1.1",
|
||||
"price": "15.5",
|
||||
"time_in_force": "gtc",
|
||||
"iceberg": "0",
|
||||
"left": "0",
|
||||
"fill_price": "17.05",
|
||||
"filled_total": "17.05",
|
||||
"avg_deal_price": "15.5",
|
||||
"fee": "0.0000018",
|
||||
"fee_currency": "SOL",
|
||||
"point_fee": "0",
|
||||
"gt_fee": "0",
|
||||
"gt_maker_fee": "0",
|
||||
"gt_taker_fee": "0.0015",
|
||||
"gt_discount": True,
|
||||
"rebated_fee": "0",
|
||||
"rebated_fee_currency": "USDT",
|
||||
"exchange_response": {
|
||||
"id": "276266139423",
|
||||
"text": "apiv4",
|
||||
"create_time": "1674493798",
|
||||
"update_time": "1674493798",
|
||||
"create_time_ms": "1674493798550",
|
||||
"update_time_ms": "1674493798550",
|
||||
"status": "closed",
|
||||
"currency_pair": "SOL_USDT",
|
||||
"type": "limit",
|
||||
"account": "spot",
|
||||
"side": "buy",
|
||||
"amount": "1.1",
|
||||
"price": "15.5",
|
||||
"time_in_force": "gtc",
|
||||
"iceberg": "0",
|
||||
"left": "0",
|
||||
"fill_price": "17.05",
|
||||
"filled_total": "17.05",
|
||||
"avg_deal_price": "15.5",
|
||||
"fee": "0.0000018",
|
||||
"fee_currency": "SOL",
|
||||
"point_fee": "0",
|
||||
"gt_fee": "0",
|
||||
"gt_maker_fee": "0",
|
||||
"gt_taker_fee": "0.0015",
|
||||
"gt_discount": True,
|
||||
"rebated_fee": "0",
|
||||
"rebated_fee_currency": "USDT",
|
||||
},
|
||||
"pair": "SOL/USDT",
|
||||
"expected": {
|
||||
"symbol": "SOL/USDT",
|
||||
"orderId": "3551312894",
|
||||
"timestamp": 1674493798550,
|
||||
"datetime": "2023-03-25T15:49:58.550Z",
|
||||
"price": 15.5,
|
||||
"status": "open",
|
||||
"amount": 1.1,
|
||||
},
|
||||
},
|
||||
{
|
||||
# market order
|
||||
"id": "276401180529",
|
||||
"text": "apiv4",
|
||||
"create_time": "1674493798",
|
||||
"update_time": "1674493798",
|
||||
"create_time_ms": "1674493798550",
|
||||
"update_time_ms": "1674493798550",
|
||||
"status": "cancelled",
|
||||
"currency_pair": "SOL_USDT",
|
||||
"type": "market",
|
||||
"account": "spot",
|
||||
"side": "buy",
|
||||
"amount": "17.05",
|
||||
"price": "0",
|
||||
"time_in_force": "ioc",
|
||||
"iceberg": "0",
|
||||
"left": "0.0000000016228",
|
||||
"fill_price": "17.05",
|
||||
"filled_total": "17.05",
|
||||
"avg_deal_price": "15.5",
|
||||
"fee": "0",
|
||||
"fee_currency": "SOL",
|
||||
"point_fee": "0.0199999999967544",
|
||||
"gt_fee": "0",
|
||||
"gt_maker_fee": "0",
|
||||
"gt_taker_fee": "0",
|
||||
"gt_discount": False,
|
||||
"rebated_fee": "0",
|
||||
"rebated_fee_currency": "USDT",
|
||||
"exchange_response": {
|
||||
# market order
|
||||
"id": "276401180529",
|
||||
"text": "apiv4",
|
||||
"create_time": "1674493798",
|
||||
"update_time": "1674493798",
|
||||
"create_time_ms": "1674493798550",
|
||||
"update_time_ms": "1674493798550",
|
||||
"status": "cancelled",
|
||||
"currency_pair": "SOL_USDT",
|
||||
"type": "market",
|
||||
"account": "spot",
|
||||
"side": "buy",
|
||||
"amount": "17.05",
|
||||
"price": "0",
|
||||
"time_in_force": "ioc",
|
||||
"iceberg": "0",
|
||||
"left": "0.0000000016228",
|
||||
"fill_price": "17.05",
|
||||
"filled_total": "17.05",
|
||||
"avg_deal_price": "15.5",
|
||||
"fee": "0",
|
||||
"fee_currency": "SOL",
|
||||
"point_fee": "0.0199999999967544",
|
||||
"gt_fee": "0",
|
||||
"gt_maker_fee": "0",
|
||||
"gt_taker_fee": "0",
|
||||
"gt_discount": False,
|
||||
"rebated_fee": "0",
|
||||
"rebated_fee_currency": "USDT",
|
||||
},
|
||||
"pair": "SOL/USDT",
|
||||
"expected": {
|
||||
"symbol": "SOL/USDT",
|
||||
"orderId": "3551312894",
|
||||
"timestamp": 1674493798550,
|
||||
"datetime": "2023-03-25T15:49:58.550Z",
|
||||
"price": 15.5,
|
||||
"status": "open",
|
||||
"amount": 1.1,
|
||||
},
|
||||
},
|
||||
],
|
||||
"sample_my_trades": [
|
||||
@@ -263,19 +348,31 @@ EXCHANGES = {
|
||||
"leverage_in_spot_market": True,
|
||||
"sample_order": [
|
||||
{
|
||||
"orderId": "1274754916287346280",
|
||||
"orderLinkId": "1666798627015730",
|
||||
"symbol": "SOLUSDT",
|
||||
"createdTime": "1674493798550",
|
||||
"price": "15.5",
|
||||
"qty": "1.1",
|
||||
"orderType": "Limit",
|
||||
"side": "Buy",
|
||||
"orderStatus": "New",
|
||||
"timeInForce": "GTC",
|
||||
"accountId": "5555555",
|
||||
"execQty": "0",
|
||||
"orderCategory": "0",
|
||||
"exchange_response": {
|
||||
"orderId": "1274754916287346280",
|
||||
"orderLinkId": "1666798627015730",
|
||||
"symbol": "SOLUSDT",
|
||||
"createdTime": "1674493798550",
|
||||
"price": "15.5",
|
||||
"qty": "1.1",
|
||||
"orderType": "Limit",
|
||||
"side": "Buy",
|
||||
"orderStatus": "New",
|
||||
"timeInForce": "GTC",
|
||||
"accountId": "5555555",
|
||||
"execQty": "0",
|
||||
"orderCategory": "0",
|
||||
},
|
||||
"pair": "SOL/USDT",
|
||||
"expected": {
|
||||
"symbol": "SOL/USDT",
|
||||
"orderId": "3551312894",
|
||||
"timestamp": 1674493798550,
|
||||
"datetime": "2023-03-25T15:49:58.550Z",
|
||||
"price": 15.5,
|
||||
"status": "open",
|
||||
"amount": 1.1,
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
@@ -309,35 +406,71 @@ EXCHANGES = {
|
||||
"futures": False,
|
||||
"sample_order": [
|
||||
{
|
||||
"symbol": "SOL-USDT",
|
||||
"orderId": "1762393630149869568",
|
||||
"transactTime": "1674493798550",
|
||||
"price": "15.5",
|
||||
"stopPrice": "0",
|
||||
"origQty": "1.1",
|
||||
"executedQty": "1.1",
|
||||
"cummulativeQuoteQty": "17.05",
|
||||
"status": "FILLED",
|
||||
"type": "LIMIT",
|
||||
"side": "BUY",
|
||||
"clientOrderID": "",
|
||||
"exchange_response": {
|
||||
"symbol": "SOL-USDT",
|
||||
"orderId": "1762393630149869568",
|
||||
"transactTime": "1674493798550",
|
||||
"price": "15.5",
|
||||
"stopPrice": "0",
|
||||
"origQty": "1.1",
|
||||
"executedQty": "1.1",
|
||||
"cummulativeQuoteQty": "17.05",
|
||||
"status": "FILLED",
|
||||
"type": "LIMIT",
|
||||
"side": "BUY",
|
||||
"clientOrderID": "",
|
||||
},
|
||||
"pair": "SOL/USDT",
|
||||
"expected": {
|
||||
"symbol": "SOL/USDT",
|
||||
"orderId": "3551312894",
|
||||
"timestamp": 1674493798550,
|
||||
"datetime": "2023-03-25T15:49:58.550Z",
|
||||
"price": 15.5,
|
||||
"status": "open",
|
||||
"amount": 1.1,
|
||||
},
|
||||
},
|
||||
{
|
||||
"symbol": "SOL-USDT",
|
||||
"orderId": "1762393630149869568",
|
||||
"transactTime": "1674493798550",
|
||||
"price": "15.5",
|
||||
"stopPrice": "0",
|
||||
"origQty": "1.1",
|
||||
"executedQty": "1.1",
|
||||
"cummulativeQuoteQty": "17.05",
|
||||
"status": "FILLED",
|
||||
"type": "MARKET",
|
||||
"side": "BUY",
|
||||
"clientOrderID": "",
|
||||
"exchange_response": {
|
||||
"symbol": "SOL-USDT",
|
||||
"orderId": "1762393630149869568",
|
||||
"transactTime": "1674493798550",
|
||||
"price": "15.5",
|
||||
"stopPrice": "0",
|
||||
"origQty": "1.1",
|
||||
"executedQty": "1.1",
|
||||
"cummulativeQuoteQty": "17.05",
|
||||
"status": "FILLED",
|
||||
"type": "MARKET",
|
||||
"side": "BUY",
|
||||
"clientOrderID": "",
|
||||
},
|
||||
"pair": "SOL/USDT",
|
||||
"expected": {
|
||||
"symbol": "SOL/USDT",
|
||||
"orderId": "3551312894",
|
||||
"timestamp": 1674493798550,
|
||||
"datetime": "2023-03-25T15:49:58.550Z",
|
||||
"price": 15.5,
|
||||
"status": "open",
|
||||
"amount": 1.1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"hyperliquid": {
|
||||
"pair": "PURR/USDC",
|
||||
"stake_currency": "USDC",
|
||||
"hasQuoteVolume": False,
|
||||
"timeframe": "1h",
|
||||
"futures": True,
|
||||
"orderbook_max_entries": 20,
|
||||
"futures_pair": "BTC/USDC:USDC",
|
||||
"hasQuoteVolumeFutures": True,
|
||||
"leverage_tiers_public": False,
|
||||
"leverage_in_spot_market": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -397,6 +530,7 @@ def get_futures_exchange(exchange_name, exchange_conf, class_mocker):
|
||||
class_mocker.patch("freqtrade.exchange.okx.Okx.additional_exchange_init")
|
||||
class_mocker.patch("freqtrade.exchange.binance.Binance.additional_exchange_init")
|
||||
class_mocker.patch("freqtrade.exchange.bybit.Bybit.additional_exchange_init")
|
||||
class_mocker.patch("freqtrade.exchange.gate.Gate.additional_exchange_init")
|
||||
class_mocker.patch(f"{EXMS}.load_cached_leverage_tiers", return_value=None)
|
||||
class_mocker.patch(f"{EXMS}.cache_leverage_tiers")
|
||||
|
||||
@@ -423,14 +557,17 @@ def exchange_mode(request):
|
||||
def exchange_ws(request, exchange_conf, exchange_mode, class_mocker):
|
||||
class_mocker.patch("freqtrade.exchange.bybit.Bybit.additional_exchange_init")
|
||||
exchange_conf["exchange"]["enable_ws"] = True
|
||||
exchange_param = EXCHANGES[request.param]
|
||||
if exchange_param.get("skip_ws_tests"):
|
||||
pytest.skip(f"{request.param} does not support websocket tests.")
|
||||
if exchange_mode == "spot":
|
||||
exchange, name = get_exchange(request.param, exchange_conf)
|
||||
pair = EXCHANGES[request.param]["pair"]
|
||||
elif EXCHANGES[request.param].get("futures"):
|
||||
pair = exchange_param["pair"]
|
||||
elif exchange_param.get("futures"):
|
||||
exchange, name = get_futures_exchange(
|
||||
request.param, exchange_conf, class_mocker=class_mocker
|
||||
)
|
||||
pair = EXCHANGES[request.param]["futures_pair"]
|
||||
pair = exchange_param["futures_pair"]
|
||||
else:
|
||||
pytest.skip("Exchange does not support futures.")
|
||||
|
||||
|
||||
@@ -61,28 +61,31 @@ class TestCCXTExchange:
|
||||
def test_ccxt_order_parse(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||
exch, exchange_name = exchange
|
||||
if orders := EXCHANGES[exchange_name].get("sample_order"):
|
||||
pair = "SOL/USDT"
|
||||
for order in orders:
|
||||
pair = order["pair"]
|
||||
exchange_response: dict = order["exchange_response"]
|
||||
|
||||
market = exch._api.markets[pair]
|
||||
po = exch._api.parse_order(order, market)
|
||||
po = exch._api.parse_order(exchange_response, market)
|
||||
expected = order["expected"]
|
||||
assert isinstance(po["id"], str)
|
||||
assert po["id"] is not None
|
||||
if len(order.keys()) < 5:
|
||||
if len(exchange_response.keys()) < 5:
|
||||
# Kucoin case
|
||||
assert po["status"] is None
|
||||
continue
|
||||
assert po["timestamp"] == 1674493798550
|
||||
assert po["timestamp"] == expected["timestamp"]
|
||||
assert isinstance(po["datetime"], str)
|
||||
assert isinstance(po["timestamp"], int)
|
||||
assert isinstance(po["price"], float)
|
||||
assert po["price"] == 15.5
|
||||
assert po["price"] == expected["price"]
|
||||
if po["status"] == "closed":
|
||||
# Filled orders should have average assigned.
|
||||
assert isinstance(po["average"], float)
|
||||
assert po["average"] == 15.5
|
||||
assert po["symbol"] == pair
|
||||
assert isinstance(po["amount"], float)
|
||||
assert po["amount"] == 1.1
|
||||
assert po["amount"] == expected["amount"]
|
||||
assert isinstance(po["status"], str)
|
||||
else:
|
||||
pytest.skip(f"No sample order available for exchange {exchange_name}")
|
||||
@@ -118,9 +121,10 @@ class TestCCXTExchange:
|
||||
tickers = exch.get_tickers()
|
||||
assert pair in tickers
|
||||
assert "ask" in tickers[pair]
|
||||
assert tickers[pair]["ask"] is not None
|
||||
assert "bid" in tickers[pair]
|
||||
assert tickers[pair]["bid"] is not None
|
||||
if EXCHANGES[exchangename].get("tickers_have_bid_ask"):
|
||||
assert tickers[pair]["bid"] is not None
|
||||
assert tickers[pair]["ask"] is not None
|
||||
assert "quoteVolume" in tickers[pair]
|
||||
if EXCHANGES[exchangename].get("hasQuoteVolume"):
|
||||
assert tickers[pair]["quoteVolume"] is not None
|
||||
@@ -150,9 +154,10 @@ class TestCCXTExchange:
|
||||
|
||||
ticker = exch.fetch_ticker(pair)
|
||||
assert "ask" in ticker
|
||||
assert ticker["ask"] is not None
|
||||
assert "bid" in ticker
|
||||
assert ticker["bid"] is not None
|
||||
if EXCHANGES[exchangename].get("tickers_have_bid_ask"):
|
||||
assert ticker["ask"] is not None
|
||||
assert ticker["bid"] is not None
|
||||
assert "quoteVolume" in ticker
|
||||
if EXCHANGES[exchangename].get("hasQuoteVolume"):
|
||||
assert ticker["quoteVolume"] is not None
|
||||
|
||||
@@ -3484,16 +3484,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])
|
||||
@@ -3845,7 +3846,7 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock
|
||||
assert log_has(
|
||||
"Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, "
|
||||
"is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed) failed: "
|
||||
"myTrade-Dict empty found",
|
||||
"myTrade-dict empty found",
|
||||
caplog,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from datetime import timedelta
|
||||
from typing import NamedTuple, Optional
|
||||
from typing import NamedTuple
|
||||
|
||||
from pandas import DataFrame
|
||||
|
||||
@@ -20,7 +20,7 @@ class BTrade(NamedTuple):
|
||||
exit_reason: ExitType
|
||||
open_tick: int
|
||||
close_tick: int
|
||||
enter_tag: Optional[str] = None
|
||||
enter_tag: str | None = None
|
||||
is_short: bool = False
|
||||
|
||||
|
||||
@@ -36,15 +36,15 @@ class BTContainer(NamedTuple):
|
||||
profit_perc: float
|
||||
trailing_stop: bool = False
|
||||
trailing_only_offset_is_reached: bool = False
|
||||
trailing_stop_positive: Optional[float] = None
|
||||
trailing_stop_positive: float | None = None
|
||||
trailing_stop_positive_offset: float = 0.0
|
||||
use_exit_signal: bool = False
|
||||
use_custom_stoploss: bool = False
|
||||
custom_entry_price: Optional[float] = None
|
||||
custom_exit_price: Optional[float] = None
|
||||
custom_entry_price: float | None = None
|
||||
custom_exit_price: float | None = None
|
||||
leverage: float = 1.0
|
||||
timeout: Optional[int] = None
|
||||
adjust_entry_price: Optional[float] = None
|
||||
timeout: int | None = None
|
||||
adjust_entry_price: float | None = None
|
||||
|
||||
|
||||
def _get_frame_time_from_offset(offset):
|
||||
|
||||
@@ -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,
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -189,7 +189,6 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
|
||||
"--timeframe",
|
||||
"1m",
|
||||
"--enable-position-stacking",
|
||||
"--disable-max-market-positions",
|
||||
"--timerange",
|
||||
":100",
|
||||
"--export-filename",
|
||||
@@ -214,10 +213,6 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
|
||||
assert "position_stacking" in config
|
||||
assert log_has("Parameter --enable-position-stacking detected ...", caplog)
|
||||
|
||||
assert "use_max_market_positions" in config
|
||||
assert log_has("Parameter --disable-max-market-positions detected ...", caplog)
|
||||
assert log_has("max_open_trades set to unlimited ...", caplog)
|
||||
|
||||
assert "timerange" in config
|
||||
assert log_has("Parameter --timerange detected: {} ...".format(config["timerange"]), caplog)
|
||||
|
||||
@@ -637,7 +632,7 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None:
|
||||
trade = backtesting._enter_trade(pair, row=row, direction="short")
|
||||
assert pytest.approx(trade.liquidation_price) == 0.11787191
|
||||
assert pytest.approx(trade.orders[0].cost) == (
|
||||
trade.stake_amount * trade.leverage + trade.fee_open
|
||||
trade.stake_amount * trade.leverage * (1 + fee.return_value)
|
||||
)
|
||||
assert pytest.approx(trade.orders[-1].stake_amount) == trade.stake_amount
|
||||
|
||||
@@ -1681,6 +1676,131 @@ def test_backtest_multi_pair_detail(
|
||||
assert len(evaluate_result_multi(results["results"], "5m", 1)) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("use_detail", [True, False])
|
||||
@pytest.mark.parametrize("pair", ["ADA/USDT", "LTC/USDT"])
|
||||
@pytest.mark.parametrize("tres", [0, 20, 30])
|
||||
def test_backtest_multi_pair_detail_simplified(
|
||||
default_conf_usdt,
|
||||
fee,
|
||||
mocker,
|
||||
tres,
|
||||
pair,
|
||||
use_detail,
|
||||
):
|
||||
"""
|
||||
literally the same as test_backtest_multi_pair_detail
|
||||
but with an "always enter" strategy, exiting after about half of the candle duration.
|
||||
"""
|
||||
|
||||
def _always_buy(dataframe, metadata):
|
||||
"""
|
||||
Buy every xth candle - sell every other xth -2 (hold on to pairs a bit)
|
||||
"""
|
||||
dataframe["enter_long"] = 1
|
||||
dataframe["enter_short"] = 0
|
||||
dataframe["exit_short"] = 0
|
||||
return dataframe
|
||||
|
||||
def custom_exit(
|
||||
trade: Trade,
|
||||
current_time: datetime,
|
||||
**kwargs,
|
||||
) -> str | bool | None:
|
||||
# Exit within the same candle.
|
||||
if (trade.open_date_utc + timedelta(minutes=20)) < current_time:
|
||||
return "exit after 20 minutes"
|
||||
|
||||
default_conf_usdt.update(
|
||||
{
|
||||
"runmode": "backtest",
|
||||
"stoploss": -1.0,
|
||||
"minimal_roi": {"0": 100},
|
||||
}
|
||||
)
|
||||
|
||||
if use_detail:
|
||||
default_conf_usdt["timeframe_detail"] = "5m"
|
||||
|
||||
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
||||
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf"))
|
||||
mocker.patch(f"{EXMS}.get_fee", fee)
|
||||
patch_exchange(mocker)
|
||||
|
||||
raw_candles_5m = generate_test_data("5m", 1000, "2022-01-03 12:00:00+00:00")
|
||||
raw_candles = ohlcv_fill_up_missing_data(raw_candles_5m, "1h", "dummy")
|
||||
|
||||
pairs = ["ADA/USDT", "DASH/USDT", "ETH/USDT", "LTC/USDT", "NXT/USDT"]
|
||||
data = {pair: raw_candles for pair in pairs}
|
||||
detail_data = {pair: raw_candles_5m for pair in pairs}
|
||||
|
||||
# Only use 500 lines to increase performance
|
||||
data = trim_dictlist(data, -200)
|
||||
|
||||
# Remove data for one pair from the beginning of the data
|
||||
if tres > 0:
|
||||
data[pair] = data[pair][tres:].reset_index()
|
||||
default_conf_usdt["timeframe"] = "1h"
|
||||
default_conf_usdt["max_open_trades"] = 3
|
||||
|
||||
backtesting = Backtesting(default_conf_usdt)
|
||||
vr_spy = mocker.spy(backtesting, "validate_row")
|
||||
bl_spy = mocker.spy(backtesting, "backtest_loop")
|
||||
backtesting.detail_data = detail_data
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
backtesting.strategy.bot_loop_start = MagicMock()
|
||||
backtesting.strategy.advise_entry = _always_buy # Override
|
||||
backtesting.strategy.advise_exit = _always_buy # Override
|
||||
backtesting.strategy.custom_exit = custom_exit # Override
|
||||
|
||||
processed = backtesting.strategy.advise_all_indicators(data)
|
||||
min_date, max_date = get_timerange(processed)
|
||||
|
||||
backtest_conf = {
|
||||
"processed": deepcopy(processed),
|
||||
"start_date": min_date,
|
||||
"end_date": max_date,
|
||||
}
|
||||
|
||||
results = backtesting.backtest(**backtest_conf)
|
||||
|
||||
# bot_loop_start is called once per candle.
|
||||
# assert backtesting.strategy.bot_loop_start.call_count == 83
|
||||
# Validated row once per candle and pair
|
||||
assert vr_spy.call_count == 415
|
||||
|
||||
if use_detail:
|
||||
# Backtest loop is called once per candle per pair
|
||||
# Exact numbers depend on trade state - but should be around 3_800
|
||||
assert bl_spy.call_count > 3_350
|
||||
assert bl_spy.call_count < 3_800
|
||||
else:
|
||||
assert bl_spy.call_count < 995
|
||||
|
||||
# Make sure we have parallel trades
|
||||
assert len(evaluate_result_multi(results["results"], "1h", 2)) > 0
|
||||
# make sure we don't have trades with more than configured max_open_trades
|
||||
assert len(evaluate_result_multi(results["results"], "1h", 3)) == 0
|
||||
|
||||
# # Cached data correctly removed amounts
|
||||
offset = 1 if tres == 0 else 0
|
||||
removed_candles = len(data[pair]) - offset
|
||||
assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, "1h")[0]) == removed_candles
|
||||
assert (
|
||||
len(backtesting.dataprovider.get_analyzed_dataframe("NXT/USDT", "1h")[0])
|
||||
== len(data["NXT/USDT"]) - 1
|
||||
)
|
||||
|
||||
backtesting.strategy.max_open_trades = 1
|
||||
backtesting.config.update({"max_open_trades": 1})
|
||||
backtest_conf = {
|
||||
"processed": deepcopy(processed),
|
||||
"start_date": min_date,
|
||||
"end_date": max_date,
|
||||
}
|
||||
results = backtesting.backtest(**backtest_conf)
|
||||
assert len(evaluate_result_multi(results["results"], "1h", 1)) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("use_detail", [True, False])
|
||||
def test_backtest_multi_pair_long_short_switch(
|
||||
default_conf_usdt,
|
||||
@@ -1774,7 +1894,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
|
||||
|
||||
@@ -1784,7 +1904,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):
|
||||
@@ -1811,14 +1931,12 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||
"--timerange",
|
||||
"1510694220-1510700340",
|
||||
"--enable-position-stacking",
|
||||
"--disable-max-market-positions",
|
||||
]
|
||||
args = get_args(args)
|
||||
start_backtesting(args)
|
||||
# check the logs, that will contain the backtest result
|
||||
exists = [
|
||||
"Parameter -i/--timeframe detected ... Using timeframe: 1m ...",
|
||||
"Ignoring max_open_trades (--disable-max-market-positions was used) ...",
|
||||
"Parameter --timerange detected: 1510694220-1510700340 ...",
|
||||
f"Using data directory: {testdatadir} ...",
|
||||
"Loading data from 2017-11-14 20:57:00 up to 2017-11-14 22:59:00 (0 days).",
|
||||
@@ -1892,7 +2010,6 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
"--timerange",
|
||||
"1510694220-1510700340",
|
||||
"--enable-position-stacking",
|
||||
"--disable-max-market-positions",
|
||||
"--strategy-list",
|
||||
CURRENT_TEST_STRATEGY,
|
||||
"StrategyTestV2",
|
||||
@@ -1909,7 +2026,6 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
# check the logs, that will contain the backtest result
|
||||
exists = [
|
||||
"Parameter -i/--timeframe detected ... Using timeframe: 1m ...",
|
||||
"Ignoring max_open_trades (--disable-max-market-positions was used) ...",
|
||||
"Parameter --timerange detected: 1510694220-1510700340 ...",
|
||||
f"Using data directory: {testdatadir} ...",
|
||||
"Loading data from 2017-11-14 20:57:00 up to 2017-11-14 22:59:00 (0 days).",
|
||||
@@ -2030,7 +2146,6 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
||||
"--timerange",
|
||||
"1510694220-1510700340",
|
||||
"--enable-position-stacking",
|
||||
"--disable-max-market-positions",
|
||||
"--breakdown",
|
||||
"day",
|
||||
"--strategy-list",
|
||||
@@ -2043,7 +2158,6 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
||||
# check the logs, that will contain the backtest result
|
||||
exists = [
|
||||
"Parameter -i/--timeframe detected ... Using timeframe: 1m ...",
|
||||
"Ignoring max_open_trades (--disable-max-market-positions was used) ...",
|
||||
"Parameter --timerange detected: 1510694220-1510700340 ...",
|
||||
f"Using data directory: {testdatadir} ...",
|
||||
"Loading data from 2017-11-14 20:57:00 up to 2017-11-14 22:59:00 (0 days).",
|
||||
@@ -2468,7 +2582,6 @@ def test_backtest_start_multi_strat_caching(
|
||||
"--timerange",
|
||||
"1510694220-1510700340",
|
||||
"--enable-position-stacking",
|
||||
"--disable-max-market-positions",
|
||||
"--cache",
|
||||
cache,
|
||||
"--strategy-list",
|
||||
@@ -2495,7 +2608,6 @@ def test_backtest_start_multi_strat_caching(
|
||||
exists = [
|
||||
"Running backtesting for Strategy StrategyTestV2",
|
||||
"Running backtesting for Strategy StrategyTestV3",
|
||||
"Ignoring max_open_trades (--disable-max-market-positions was used) ...",
|
||||
"Backtesting with data from 2017-11-14 21:17:00 up to 2017-11-14 22:59:00 (0 days).",
|
||||
]
|
||||
elif run_id == "2" and min_backtest_date < start_time:
|
||||
@@ -2508,7 +2620,6 @@ def test_backtest_start_multi_strat_caching(
|
||||
exists = [
|
||||
"Reusing result of previous backtest for StrategyTestV2",
|
||||
"Running backtesting for Strategy StrategyTestV3",
|
||||
"Ignoring max_open_trades (--disable-max-market-positions was used) ...",
|
||||
"Backtesting with data from 2017-11-14 21:17:00 up to 2017-11-14 22:59:00 (0 days).",
|
||||
]
|
||||
assert backtestmock.call_count == 1
|
||||
|
||||
+186
-150
@@ -14,7 +14,7 @@ from freqtrade.data.history import load_data
|
||||
from freqtrade.enums import ExitType, RunMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.optimize.hyperopt import Hyperopt
|
||||
from freqtrade.optimize.hyperopt_auto import HyperOptAuto
|
||||
from freqtrade.optimize.hyperopt.hyperopt_auto import HyperOptAuto
|
||||
from freqtrade.optimize.hyperopt_tools import HyperoptTools
|
||||
from freqtrade.optimize.optimize_reports import generate_strategy_stats
|
||||
from freqtrade.optimize.space import SKDecimal
|
||||
@@ -102,7 +102,6 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
|
||||
"--timerange",
|
||||
":100",
|
||||
"--enable-position-stacking",
|
||||
"--disable-max-market-positions",
|
||||
"--epochs",
|
||||
"1000",
|
||||
"--spaces",
|
||||
@@ -126,10 +125,6 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
|
||||
assert "position_stacking" in config
|
||||
assert log_has("Parameter --enable-position-stacking detected ...", caplog)
|
||||
|
||||
assert "use_max_market_positions" in config
|
||||
assert log_has("Parameter --disable-max-market-positions detected ...", caplog)
|
||||
assert log_has("max_open_trades set to unlimited ...", caplog)
|
||||
|
||||
assert "timerange" in config
|
||||
assert log_has("Parameter --timerange detected: {} ...".format(config["timerange"]), caplog)
|
||||
|
||||
@@ -227,7 +222,7 @@ def test_start_no_data(mocker, hyperopt_conf, tmp_path) -> None:
|
||||
patched_configuration_load_config_file(mocker, hyperopt_conf)
|
||||
mocker.patch("freqtrade.data.history.load_pair_history", MagicMock(return_value=pd.DataFrame))
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.get_timerange",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
|
||||
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
|
||||
)
|
||||
|
||||
@@ -320,12 +315,17 @@ def test_roi_table_generation(hyperopt) -> None:
|
||||
"roi_p3": 3,
|
||||
}
|
||||
|
||||
assert hyperopt.custom_hyperopt.generate_roi_table(params) == {0: 6, 15: 3, 25: 1, 30: 0}
|
||||
assert hyperopt.hyperopter.custom_hyperopt.generate_roi_table(params) == {
|
||||
0: 6,
|
||||
15: 3,
|
||||
25: 1,
|
||||
30: 0,
|
||||
}
|
||||
|
||||
|
||||
def test_params_no_optimize_details(hyperopt) -> None:
|
||||
hyperopt.config["spaces"] = ["buy"]
|
||||
res = hyperopt._get_no_optimize_details()
|
||||
hyperopt.hyperopter.config["spaces"] = ["buy"]
|
||||
res = hyperopt.hyperopter._get_no_optimize_details()
|
||||
assert isinstance(res, dict)
|
||||
assert "trailing" in res
|
||||
assert res["trailing"]["trailing_stop"] is False
|
||||
@@ -338,21 +338,23 @@ def test_params_no_optimize_details(hyperopt) -> None:
|
||||
|
||||
|
||||
def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
|
||||
dumper = mocker.patch("freqtrade.optimize.hyperopt.dump")
|
||||
dumper = mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump")
|
||||
dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result")
|
||||
mocker.patch("freqtrade.optimize.hyperopt.calculate_market_change", return_value=1.5)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json")
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.calculate_market_change", return_value=1.5
|
||||
)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
|
||||
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.backtesting.Backtesting.load_bt_data",
|
||||
MagicMock(return_value=(MagicMock(), None)),
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.get_timerange",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
|
||||
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
|
||||
)
|
||||
# Dummy-reduce points to ensure scikit-learn is forced to generate new values
|
||||
mocker.patch("freqtrade.optimize.hyperopt.INITIAL_POINTS", 2)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.INITIAL_POINTS", 2)
|
||||
|
||||
parallel = mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel",
|
||||
@@ -372,8 +374,8 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
|
||||
del hyperopt_conf["timeframe"]
|
||||
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
@@ -384,10 +386,12 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
|
||||
# Should be called for historical candle data
|
||||
assert dumper.call_count == 1
|
||||
assert dumper2.call_count == 1
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
|
||||
assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"]
|
||||
assert hasattr(hyperopt.backtesting, "_position_stacking")
|
||||
assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_exit")
|
||||
assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_entry")
|
||||
assert (
|
||||
hyperopt.hyperopter.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"]
|
||||
)
|
||||
assert hasattr(hyperopt.hyperopter.backtesting, "_position_stacking")
|
||||
|
||||
|
||||
def test_hyperopt_format_results(hyperopt):
|
||||
@@ -466,7 +470,7 @@ def test_hyperopt_format_results(hyperopt):
|
||||
|
||||
def test_populate_indicators(hyperopt, testdatadir) -> None:
|
||||
data = load_data(testdatadir, "1m", ["UNITTEST/BTC"], fill_up_missing=True)
|
||||
dataframes = hyperopt.backtesting.strategy.advise_all_indicators(data)
|
||||
dataframes = hyperopt.hyperopter.backtesting.strategy.advise_all_indicators(data)
|
||||
dataframe = dataframes["UNITTEST/BTC"]
|
||||
|
||||
# Check if some indicators are generated. We will not test all of them
|
||||
@@ -526,15 +530,20 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
|
||||
"final_balance": 1000,
|
||||
}
|
||||
|
||||
mocker.patch("freqtrade.optimize.hyperopt.Backtesting.backtest", return_value=backtest_result)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.get_timerange",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.Backtesting.backtest",
|
||||
return_value=backtest_result,
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
|
||||
return_value=(dt_utc(2017, 12, 10), dt_utc(2017, 12, 13)),
|
||||
)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.object(Path, "open")
|
||||
mocker.patch("freqtrade.configuration.config_validation.validate_config_schema")
|
||||
mocker.patch("freqtrade.optimize.hyperopt.load", return_value={"XRP/BTC": None})
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.load", return_value={"XRP/BTC": None}
|
||||
)
|
||||
|
||||
optimizer_param = {
|
||||
"buy_plusdi": 0.02,
|
||||
@@ -594,10 +603,12 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
|
||||
}
|
||||
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.min_date = dt_utc(2017, 12, 10)
|
||||
hyperopt.max_date = dt_utc(2017, 12, 13)
|
||||
hyperopt.init_spaces()
|
||||
generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values()))
|
||||
hyperopt.hyperopter.min_date = dt_utc(2017, 12, 10)
|
||||
hyperopt.hyperopter.max_date = dt_utc(2017, 12, 13)
|
||||
hyperopt.hyperopter.init_spaces()
|
||||
generate_optimizer_value = hyperopt.hyperopter.generate_optimizer(
|
||||
list(optimizer_param.values())
|
||||
)
|
||||
assert generate_optimizer_value == response_expected
|
||||
|
||||
|
||||
@@ -608,8 +619,8 @@ def test_clean_hyperopt(mocker, hyperopt_conf, caplog):
|
||||
"freqtrade.strategy.hyper.HyperStrategyMixin.load_params_from_file",
|
||||
MagicMock(return_value={}),
|
||||
)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.Path.is_file", MagicMock(return_value=True))
|
||||
unlinkmock = mocker.patch("freqtrade.optimize.hyperopt.Path.unlink", MagicMock())
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.Path.is_file", MagicMock(return_value=True))
|
||||
unlinkmock = mocker.patch("freqtrade.optimize.hyperopt.hyperopt.Path.unlink", MagicMock())
|
||||
h = Hyperopt(hyperopt_conf)
|
||||
|
||||
assert unlinkmock.call_count == 2
|
||||
@@ -617,17 +628,19 @@ def test_clean_hyperopt(mocker, hyperopt_conf, caplog):
|
||||
|
||||
|
||||
def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
|
||||
dumper = mocker.patch("freqtrade.optimize.hyperopt.dump")
|
||||
dumper = mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump")
|
||||
dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result")
|
||||
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json")
|
||||
mocker.patch("freqtrade.optimize.hyperopt.calculate_market_change", return_value=1.5)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.calculate_market_change", return_value=1.5
|
||||
)
|
||||
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.backtesting.Backtesting.load_bt_data",
|
||||
MagicMock(return_value=(MagicMock(), None)),
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.get_timerange",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
|
||||
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
|
||||
)
|
||||
|
||||
@@ -663,8 +676,8 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
|
||||
)
|
||||
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
@@ -682,16 +695,18 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
|
||||
|
||||
|
||||
def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
|
||||
dumper = mocker.patch("freqtrade.optimize.hyperopt.dump")
|
||||
dumper = mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump")
|
||||
dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result")
|
||||
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json")
|
||||
mocker.patch("freqtrade.optimize.hyperopt.calculate_market_change", return_value=1.5)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.calculate_market_change", return_value=1.5
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.backtesting.Backtesting.load_bt_data",
|
||||
MagicMock(return_value=(MagicMock(), None)),
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.get_timerange",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
|
||||
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
|
||||
)
|
||||
|
||||
@@ -719,8 +734,8 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
|
||||
hyperopt_conf.update({"print_json": True})
|
||||
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
@@ -737,16 +752,18 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
|
||||
|
||||
|
||||
def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
|
||||
dumper = mocker.patch("freqtrade.optimize.hyperopt.dump")
|
||||
dumper = mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump")
|
||||
dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result")
|
||||
mocker.patch("freqtrade.optimize.hyperopt.calculate_market_change", return_value=1.5)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json")
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.calculate_market_change", return_value=1.5
|
||||
)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.backtesting.Backtesting.load_bt_data",
|
||||
MagicMock(return_value=(MagicMock(), None)),
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.get_timerange",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
|
||||
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
|
||||
)
|
||||
|
||||
@@ -775,8 +792,8 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
|
||||
)
|
||||
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
@@ -790,16 +807,18 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
|
||||
|
||||
|
||||
def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
|
||||
dumper = mocker.patch("freqtrade.optimize.hyperopt.dump")
|
||||
dumper = mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump")
|
||||
dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result")
|
||||
mocker.patch("freqtrade.optimize.hyperopt.calculate_market_change", return_value=1.5)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json")
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.calculate_market_change", return_value=1.5
|
||||
)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.backtesting.Backtesting.load_bt_data",
|
||||
MagicMock(return_value=(MagicMock(), None)),
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.get_timerange",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
|
||||
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
|
||||
)
|
||||
|
||||
@@ -821,8 +840,8 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
|
||||
hyperopt_conf.update({"spaces": "roi stoploss"})
|
||||
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
@@ -833,21 +852,23 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
|
||||
assert dumper.call_count == 1
|
||||
assert dumper2.call_count == 1
|
||||
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
|
||||
assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"]
|
||||
assert hasattr(hyperopt.backtesting, "_position_stacking")
|
||||
assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_exit")
|
||||
assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_entry")
|
||||
assert (
|
||||
hyperopt.hyperopter.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"]
|
||||
)
|
||||
assert hasattr(hyperopt.hyperopter.backtesting, "_position_stacking")
|
||||
|
||||
|
||||
def test_simplified_interface_all_failed(mocker, hyperopt_conf, caplog) -> None:
|
||||
mocker.patch("freqtrade.optimize.hyperopt.dump", MagicMock())
|
||||
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json")
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump", MagicMock())
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.backtesting.Backtesting.load_bt_data",
|
||||
MagicMock(return_value=(MagicMock(), None)),
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.get_timerange",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
|
||||
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
|
||||
)
|
||||
|
||||
@@ -860,34 +881,37 @@ def test_simplified_interface_all_failed(mocker, hyperopt_conf, caplog) -> None:
|
||||
)
|
||||
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt_auto.HyperOptAuto._generate_indicator_space", return_value=[]
|
||||
"freqtrade.optimize.hyperopt.hyperopt_auto.HyperOptAuto._generate_indicator_space",
|
||||
return_value=[],
|
||||
)
|
||||
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
|
||||
with pytest.raises(OperationalException, match=r"The 'protection' space is included into *"):
|
||||
hyperopt.init_spaces()
|
||||
hyperopt.hyperopter.init_spaces()
|
||||
|
||||
hyperopt.config["hyperopt_ignore_missing_space"] = True
|
||||
caplog.clear()
|
||||
hyperopt.init_spaces()
|
||||
hyperopt.hyperopter.init_spaces()
|
||||
assert log_has_re(r"The 'protection' space is included into *", caplog)
|
||||
assert hyperopt.protection_space == []
|
||||
assert hyperopt.hyperopter.protection_space == []
|
||||
|
||||
|
||||
def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
|
||||
dumper = mocker.patch("freqtrade.optimize.hyperopt.dump")
|
||||
dumper = mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump")
|
||||
dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result")
|
||||
mocker.patch("freqtrade.optimize.hyperopt.calculate_market_change", return_value=1.5)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json")
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.calculate_market_change", return_value=1.5
|
||||
)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.backtesting.Backtesting.load_bt_data",
|
||||
MagicMock(return_value=(MagicMock(), None)),
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.get_timerange",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
|
||||
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
|
||||
)
|
||||
|
||||
@@ -909,8 +933,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
|
||||
hyperopt_conf.update({"spaces": "buy"})
|
||||
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
@@ -921,23 +945,27 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
|
||||
assert dumper.called
|
||||
assert dumper.call_count == 1
|
||||
assert dumper2.call_count == 1
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
|
||||
assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"]
|
||||
assert hasattr(hyperopt.backtesting, "_position_stacking")
|
||||
assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_exit")
|
||||
assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_entry")
|
||||
assert (
|
||||
hyperopt.hyperopter.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"]
|
||||
)
|
||||
assert hasattr(hyperopt.hyperopter.backtesting, "_position_stacking")
|
||||
|
||||
|
||||
def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
|
||||
dumper = mocker.patch("freqtrade.optimize.hyperopt.dump")
|
||||
dumper = mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump")
|
||||
dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result")
|
||||
mocker.patch("freqtrade.optimize.hyperopt.calculate_market_change", return_value=1.5)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json")
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.calculate_market_change", return_value=1.5
|
||||
)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.backtesting.Backtesting.load_bt_data",
|
||||
MagicMock(return_value=(MagicMock(), None)),
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.get_timerange",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
|
||||
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
|
||||
)
|
||||
|
||||
@@ -963,8 +991,8 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
|
||||
)
|
||||
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
@@ -975,10 +1003,12 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
|
||||
assert dumper.called
|
||||
assert dumper.call_count == 1
|
||||
assert dumper2.call_count == 1
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
|
||||
assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"]
|
||||
assert hasattr(hyperopt.backtesting, "_position_stacking")
|
||||
assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_exit")
|
||||
assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_entry")
|
||||
assert (
|
||||
hyperopt.hyperopter.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"]
|
||||
)
|
||||
assert hasattr(hyperopt.hyperopter.backtesting, "_position_stacking")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -990,18 +1020,19 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
|
||||
],
|
||||
)
|
||||
def test_simplified_interface_failed(mocker, hyperopt_conf, space) -> None:
|
||||
mocker.patch("freqtrade.optimize.hyperopt.dump", MagicMock())
|
||||
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json")
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump", MagicMock())
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.backtesting.Backtesting.load_bt_data",
|
||||
MagicMock(return_value=(MagicMock(), None)),
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.get_timerange",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
|
||||
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt_auto.HyperOptAuto._generate_indicator_space", return_value=[]
|
||||
"freqtrade.optimize.hyperopt.hyperopt_auto.HyperOptAuto._generate_indicator_space",
|
||||
return_value=[],
|
||||
)
|
||||
|
||||
patch_exchange(mocker)
|
||||
@@ -1009,8 +1040,8 @@ def test_simplified_interface_failed(mocker, hyperopt_conf, space) -> None:
|
||||
hyperopt_conf.update({"spaces": space})
|
||||
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
|
||||
with pytest.raises(OperationalException, match=f"The '{space}' space is included into *"):
|
||||
hyperopt.start()
|
||||
@@ -1020,7 +1051,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmp_path, fee) -> None
|
||||
patch_exchange(mocker)
|
||||
mocker.patch(f"{EXMS}.get_fee", fee)
|
||||
# Dummy-reduce points to ensure scikit-learn is forced to generate new values
|
||||
mocker.patch("freqtrade.optimize.hyperopt.INITIAL_POINTS", 2)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.INITIAL_POINTS", 2)
|
||||
(tmp_path / "hyperopt_results").mkdir(parents=True)
|
||||
# No hyperopt needed
|
||||
hyperopt_conf.update(
|
||||
@@ -1032,32 +1063,33 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmp_path, fee) -> None
|
||||
}
|
||||
)
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert hyperopt.backtesting.strategy.bot_started is True
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is False
|
||||
opt = hyperopt.hyperopter
|
||||
opt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
|
||||
assert isinstance(opt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(opt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert opt.backtesting.strategy.bot_started is True
|
||||
assert opt.backtesting.strategy.bot_loop_started is False
|
||||
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||
assert hyperopt.backtesting.strategy.sell_rsi.value == 74
|
||||
assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value == 30
|
||||
assert hyperopt.backtesting.strategy.max_open_trades == 1
|
||||
buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range
|
||||
assert opt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert opt.backtesting.strategy.buy_rsi.value == 35
|
||||
assert opt.backtesting.strategy.sell_rsi.value == 74
|
||||
assert opt.backtesting.strategy.protection_cooldown_lookback.value == 30
|
||||
assert opt.backtesting.strategy.max_open_trades == 1
|
||||
buy_rsi_range = opt.backtesting.strategy.buy_rsi.range
|
||||
assert isinstance(buy_rsi_range, range)
|
||||
# Range from 0 - 50 (inclusive)
|
||||
assert len(list(buy_rsi_range)) == 51
|
||||
|
||||
hyperopt.start()
|
||||
# All values should've changed.
|
||||
assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value != 30
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value != 35
|
||||
assert hyperopt.backtesting.strategy.sell_rsi.value != 74
|
||||
assert hyperopt.backtesting.strategy.max_open_trades != 1
|
||||
assert opt.backtesting.strategy.protection_cooldown_lookback.value != 30
|
||||
assert opt.backtesting.strategy.buy_rsi.value != 35
|
||||
assert opt.backtesting.strategy.sell_rsi.value != 74
|
||||
assert opt.backtesting.strategy.max_open_trades != 1
|
||||
|
||||
hyperopt.custom_hyperopt.generate_estimator = lambda *args, **kwargs: "ET1"
|
||||
opt.custom_hyperopt.generate_estimator = lambda *args, **kwargs: "ET1"
|
||||
with pytest.raises(OperationalException, match="Estimator ET1 not supported."):
|
||||
hyperopt.get_optimizer([], 2)
|
||||
opt.get_optimizer(2, 42, 2, 2)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||
@@ -1068,7 +1100,7 @@ def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmp_path
|
||||
mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=get_markets()))
|
||||
(tmp_path / "hyperopt_results").mkdir(parents=True)
|
||||
# Dummy-reduce points to ensure scikit-learn is forced to generate new values
|
||||
mocker.patch("freqtrade.optimize.hyperopt.INITIAL_POINTS", 2)
|
||||
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.INITIAL_POINTS", 2)
|
||||
# No hyperopt needed
|
||||
hyperopt_conf.update(
|
||||
{
|
||||
@@ -1083,21 +1115,22 @@ def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmp_path
|
||||
}
|
||||
)
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.exchange.get_max_leverage = lambda *x, **xx: 1.0
|
||||
hyperopt.backtesting.exchange.get_min_pair_stake_amount = lambda *x, **xx: 0.00001
|
||||
hyperopt.backtesting.exchange.get_max_pair_stake_amount = lambda *x, **xx: 100.0
|
||||
hyperopt.backtesting.exchange._markets = get_markets()
|
||||
opt = hyperopt.hyperopter
|
||||
opt.backtesting.exchange.get_max_leverage = lambda *x, **xx: 1.0
|
||||
opt.backtesting.exchange.get_min_pair_stake_amount = lambda *x, **xx: 0.00001
|
||||
opt.backtesting.exchange.get_max_pair_stake_amount = lambda *x, **xx: 100.0
|
||||
opt.backtesting.exchange._markets = get_markets()
|
||||
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert hyperopt.backtesting.strategy.bot_started is True
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is False
|
||||
assert isinstance(opt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(opt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert opt.backtesting.strategy.bot_started is True
|
||||
assert opt.backtesting.strategy.bot_loop_started is False
|
||||
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||
assert hyperopt.backtesting.strategy.sell_rsi.value == 74
|
||||
assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value == 30
|
||||
buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range
|
||||
assert opt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert opt.backtesting.strategy.buy_rsi.value == 35
|
||||
assert opt.backtesting.strategy.sell_rsi.value == 74
|
||||
assert opt.backtesting.strategy.protection_cooldown_lookback.value == 30
|
||||
buy_rsi_range = opt.backtesting.strategy.buy_rsi.range
|
||||
assert isinstance(buy_rsi_range, range)
|
||||
# Range from 0 - 50 (inclusive)
|
||||
assert len(list(buy_rsi_range)) == 51
|
||||
@@ -1121,7 +1154,7 @@ def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmp_path, fe
|
||||
}
|
||||
)
|
||||
go = mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.Hyperopt.generate_optimizer",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.HyperOptimizer.generate_optimizer",
|
||||
return_value={
|
||||
"loss": 0.05,
|
||||
"results_explanation": "foo result",
|
||||
@@ -1130,17 +1163,18 @@ def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmp_path, fe
|
||||
},
|
||||
)
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is False
|
||||
assert hyperopt.backtesting.strategy.bot_started is True
|
||||
opt = hyperopt.hyperopter
|
||||
opt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
|
||||
assert isinstance(opt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(opt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert opt.backtesting.strategy.bot_loop_started is False
|
||||
assert opt.backtesting.strategy.bot_started is True
|
||||
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||
assert hyperopt.backtesting.strategy.sell_rsi.value == 74
|
||||
assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value == 30
|
||||
buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range
|
||||
assert opt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert opt.backtesting.strategy.buy_rsi.value == 35
|
||||
assert opt.backtesting.strategy.sell_rsi.value == 74
|
||||
assert opt.backtesting.strategy.protection_cooldown_lookback.value == 30
|
||||
buy_rsi_range = opt.backtesting.strategy.buy_rsi.range
|
||||
assert isinstance(buy_rsi_range, range)
|
||||
# Range from 0 - 50 (inclusive)
|
||||
assert len(list(buy_rsi_range)) == 51
|
||||
@@ -1184,17 +1218,17 @@ def test_stake_amount_unlimited_max_open_trades(mocker, hyperopt_conf, tmp_path,
|
||||
)
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.Hyperopt._get_params_dict",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.HyperOptimizer._get_params_dict",
|
||||
return_value={"max_open_trades": -1},
|
||||
)
|
||||
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.hyperopter.custom_hyperopt, HyperOptAuto)
|
||||
|
||||
assert hyperopt.backtesting.strategy.max_open_trades == 1
|
||||
assert hyperopt.hyperopter.backtesting.strategy.max_open_trades == 1
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
assert hyperopt.backtesting.strategy.max_open_trades == 1
|
||||
assert hyperopt.hyperopter.backtesting.strategy.max_open_trades == 1
|
||||
|
||||
|
||||
def test_max_open_trades_dump(mocker, hyperopt_conf, tmp_path, fee, capsys) -> None:
|
||||
@@ -1213,11 +1247,11 @@ def test_max_open_trades_dump(mocker, hyperopt_conf, tmp_path, fee, capsys) -> N
|
||||
)
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.Hyperopt._get_params_dict",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.HyperOptimizer._get_params_dict",
|
||||
return_value={"max_open_trades": -1},
|
||||
)
|
||||
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.hyperopter.custom_hyperopt, HyperOptAuto)
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
@@ -1232,11 +1266,11 @@ def test_max_open_trades_dump(mocker, hyperopt_conf, tmp_path, fee, capsys) -> N
|
||||
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt.Hyperopt._get_params_dict",
|
||||
"freqtrade.optimize.hyperopt.hyperopt_optimizer.HyperOptimizer._get_params_dict",
|
||||
return_value={"max_open_trades": -1},
|
||||
)
|
||||
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.hyperopter.custom_hyperopt, HyperOptAuto)
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
@@ -1267,9 +1301,9 @@ def test_max_open_trades_consistency(mocker, hyperopt_conf, tmp_path, fee) -> No
|
||||
)
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.hyperopter.custom_hyperopt, HyperOptAuto)
|
||||
|
||||
hyperopt.custom_hyperopt.max_open_trades_space = lambda: [
|
||||
hyperopt.hyperopter.custom_hyperopt.max_open_trades_space = lambda: [
|
||||
Integer(1, 10, name="max_open_trades")
|
||||
]
|
||||
|
||||
@@ -1287,11 +1321,13 @@ def test_max_open_trades_consistency(mocker, hyperopt_conf, tmp_path, fee) -> No
|
||||
|
||||
return wrapper
|
||||
|
||||
hyperopt.backtesting.wallets._calculate_unlimited_stake_amount = stake_amount_interceptor(
|
||||
hyperopt.backtesting.wallets._calculate_unlimited_stake_amount
|
||||
hyperopt.hyperopter.backtesting.wallets._calculate_unlimited_stake_amount = (
|
||||
stake_amount_interceptor(
|
||||
hyperopt.hyperopter.backtesting.wallets._calculate_unlimited_stake_amount
|
||||
)
|
||||
)
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
assert hyperopt.backtesting.strategy.max_open_trades == 8
|
||||
assert hyperopt.hyperopter.backtesting.strategy.max_open_trades == 8
|
||||
assert hyperopt.config["max_open_trades"] == 8
|
||||
|
||||
@@ -95,6 +95,7 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) ->
|
||||
"MaxDrawDownRelativeHyperOptLoss",
|
||||
"CalmarHyperOptLoss",
|
||||
"ProfitDrawDownHyperOptLoss",
|
||||
"MultiMetricHyperOptLoss",
|
||||
],
|
||||
)
|
||||
def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunction) -> None:
|
||||
|
||||
@@ -2144,6 +2144,7 @@ def test_Trade_object_idem():
|
||||
"bt_trades_open",
|
||||
"bt_trades_open_pp",
|
||||
"bt_open_open_trade_count",
|
||||
"bt_open_open_trade_count_candle",
|
||||
"bt_total_profit",
|
||||
"from_json",
|
||||
)
|
||||
@@ -2682,6 +2683,36 @@ def test_select_filled_orders(fee):
|
||||
assert len(orders) == 0
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_select_filled_orders_usdt(fee):
|
||||
create_mock_trades_usdt(fee)
|
||||
|
||||
trades = Trade.get_trades().all()
|
||||
|
||||
# Closed buy order, no sell order
|
||||
orders = trades[0].select_filled_orders("buy")
|
||||
assert isinstance(orders, list)
|
||||
assert len(orders) == 1
|
||||
assert orders[0].amount == 2.0
|
||||
assert orders[0].filled == 2.0
|
||||
assert orders[0].side == "buy"
|
||||
assert orders[0].price == 10.0
|
||||
assert orders[0].stake_amount == 20
|
||||
assert orders[0].stake_amount_filled == 20
|
||||
|
||||
orders = trades[3].select_filled_orders("buy")
|
||||
assert isinstance(orders, list)
|
||||
assert len(orders) == 0
|
||||
orders = trades[3].select_filled_or_open_orders()
|
||||
assert isinstance(orders, list)
|
||||
assert len(orders) == 1
|
||||
assert orders[0].price == 2.0
|
||||
assert orders[0].amount == 10
|
||||
assert orders[0].filled == 0
|
||||
assert orders[0].stake_amount == 20
|
||||
assert orders[0].stake_amount_filled == 0
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_order_to_ccxt(limit_buy_order_open, limit_sell_order_usdt_open):
|
||||
order = Order.parse_from_ccxt_object(limit_buy_order_open, "mocked", "buy")
|
||||
|
||||
@@ -2450,7 +2450,7 @@ def test_MarketCapPairList_filter_special_no_pair_from_coingecko(
|
||||
assert pm.whitelist == []
|
||||
|
||||
|
||||
def test_MarketCapPairList_exceptions(mocker, default_conf_usdt):
|
||||
def test_MarketCapPairList_exceptions(mocker, default_conf_usdt, caplog):
|
||||
exchange = get_patched_exchange(mocker, default_conf_usdt)
|
||||
default_conf_usdt["pairlists"] = [{"method": "MarketCapPairList"}]
|
||||
with pytest.raises(OperationalException, match=r"`number_assets` not specified.*"):
|
||||
@@ -2458,13 +2458,11 @@ def test_MarketCapPairList_exceptions(mocker, default_conf_usdt):
|
||||
PairListManager(exchange, default_conf_usdt)
|
||||
|
||||
default_conf_usdt["pairlists"] = [
|
||||
{"method": "MarketCapPairList", "number_assets": 20, "max_rank": 260}
|
||||
{"method": "MarketCapPairList", "number_assets": 20, "max_rank": 500}
|
||||
]
|
||||
with pytest.raises(
|
||||
OperationalException, match="This filter only support marketcap rank up to 250."
|
||||
):
|
||||
with caplog.at_level(logging.WARNING):
|
||||
PairListManager(exchange, default_conf_usdt)
|
||||
|
||||
assert log_has_re("The max rank you have set \\(500\\) is quite high", caplog)
|
||||
# Test invalid coinmarkets list
|
||||
mocker.patch(
|
||||
"freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_categories_list",
|
||||
|
||||
@@ -206,14 +206,16 @@ def test_convert_amount(mocker):
|
||||
|
||||
def test_FtCoinGeckoApi():
|
||||
ftc = FtCoinGeckoApi()
|
||||
assert ftc._api_key == ""
|
||||
assert ftc.extra_params is None
|
||||
assert ftc.api_base_url == "https://api.coingecko.com/api/v3/"
|
||||
|
||||
# defaults to demo
|
||||
ftc = FtCoinGeckoApi(api_key="123456")
|
||||
assert ftc._api_key == "123456"
|
||||
assert ftc.extra_params is not None
|
||||
assert ftc.extra_params["x_cg_demo_api_key"] == "123456"
|
||||
assert ftc.api_base_url == "https://api.coingecko.com/api/v3/"
|
||||
|
||||
ftc = FtCoinGeckoApi(api_key="123456", is_demo=False)
|
||||
assert ftc._api_key == "123456"
|
||||
assert ftc.extra_params is not None
|
||||
assert ftc.extra_params["x_cg_pro_api_key"] == "123456"
|
||||
assert ftc.api_base_url == "https://pro-api.coingecko.com/api/v3/"
|
||||
|
||||
+105
-15
@@ -691,20 +691,22 @@ def test_api_show_config(botclient):
|
||||
|
||||
def test_api_daily(botclient, mocker, ticker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
fetch_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets),
|
||||
)
|
||||
|
||||
ftbot.config["dry_run"] = False
|
||||
mocker.patch(f"{EXMS}.get_balances", return_value=ticker)
|
||||
mocker.patch(f"{EXMS}.get_tickers", ticker)
|
||||
mocker.patch(f"{EXMS}.get_fee", fee)
|
||||
mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets))
|
||||
ftbot.wallets.update()
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/daily")
|
||||
assert_response(rc)
|
||||
assert len(rc.json()["data"]) == 7
|
||||
assert rc.json()["stake_currency"] == "BTC"
|
||||
assert rc.json()["fiat_display_currency"] == "USD"
|
||||
assert rc.json()["data"][0]["date"] == str(datetime.now(timezone.utc).date())
|
||||
response = rc.json()
|
||||
assert "data" in response
|
||||
assert len(response["data"]) == 7
|
||||
assert response["stake_currency"] == "BTC"
|
||||
assert response["fiat_display_currency"] == "USD"
|
||||
assert response["data"][0]["date"] == str(datetime.now(timezone.utc).date())
|
||||
|
||||
|
||||
def test_api_weekly(botclient, mocker, ticker, fee, markets, time_machine):
|
||||
@@ -2189,6 +2191,22 @@ def test_api_exchanges(botclient):
|
||||
}
|
||||
|
||||
|
||||
def test_list_hyperoptloss(botclient, tmp_path):
|
||||
ftbot, client = botclient
|
||||
ftbot.config["user_data_dir"] = tmp_path
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/hyperoptloss")
|
||||
assert_response(rc)
|
||||
response = rc.json()
|
||||
assert isinstance(response["loss_functions"], list)
|
||||
assert len(response["loss_functions"]) > 0
|
||||
|
||||
sharpeloss = [r for r in response["loss_functions"] if r["name"] == "SharpeHyperOptLoss"]
|
||||
assert len(sharpeloss) == 1
|
||||
assert "Sharpe Ratio calculation" in sharpeloss[0]["description"]
|
||||
assert len([r for r in response["loss_functions"] if r["name"] == "SortinoHyperOptLoss"]) == 1
|
||||
|
||||
|
||||
def test_api_freqaimodels(botclient, tmp_path, mocker):
|
||||
ftbot, client = botclient
|
||||
ftbot.config["user_data_dir"] = tmp_path
|
||||
@@ -2339,9 +2357,7 @@ def test_api_pairlists_evaluate(botclient, tmp_path, mocker):
|
||||
]
|
||||
assert response["result"]["length"] == 2
|
||||
# Patch __run_pairlists
|
||||
plm = mocker.patch(
|
||||
"freqtrade.rpc.api_server.api_background_tasks.__run_pairlist", return_value=None
|
||||
)
|
||||
plm = mocker.patch("freqtrade.rpc.api_server.api_pairlists.__run_pairlist", return_value=None)
|
||||
body = {
|
||||
"pairlists": [
|
||||
{
|
||||
@@ -2598,6 +2614,8 @@ def test_api_delete_backtest_history_entry(botclient, tmp_path: Path):
|
||||
file_path.touch()
|
||||
meta_path = file_path.with_suffix(".meta.json")
|
||||
meta_path.touch()
|
||||
market_change_path = file_path.with_name(file_path.stem + "_market_change.feather")
|
||||
market_change_path.touch()
|
||||
|
||||
rc = client_delete(client, f"{BASE_URI}/backtest/history/randomFile.json")
|
||||
assert_response(rc, 503)
|
||||
@@ -2614,6 +2632,7 @@ def test_api_delete_backtest_history_entry(botclient, tmp_path: Path):
|
||||
|
||||
assert not file_path.exists()
|
||||
assert not meta_path.exists()
|
||||
assert not market_change_path.exists()
|
||||
|
||||
|
||||
def test_api_patch_backtest_history_entry(botclient, tmp_path: Path):
|
||||
@@ -2844,3 +2863,74 @@ def test_api_ws_send_msg(default_conf, mocker, caplog):
|
||||
finally:
|
||||
ApiServer.shutdown()
|
||||
ApiServer.shutdown()
|
||||
|
||||
|
||||
def test_api_download_data(botclient, mocker, tmp_path, caplog):
|
||||
ftbot, client = botclient
|
||||
|
||||
rc = client_post(client, f"{BASE_URI}/download_data", data={})
|
||||
assert_response(rc, 503)
|
||||
assert rc.json()["detail"] == "Bot is not in the correct state."
|
||||
|
||||
ftbot.config["runmode"] = RunMode.WEBSERVER
|
||||
ftbot.config["user_data_dir"] = tmp_path
|
||||
|
||||
body = {
|
||||
"pairs": ["ETH/BTC", "XRP/BTC"],
|
||||
"timeframes": ["5m"],
|
||||
}
|
||||
|
||||
# Fail, already running
|
||||
ApiBG.download_data_running = True
|
||||
rc = client_post(client, f"{BASE_URI}/download_data", body)
|
||||
assert_response(rc, 400)
|
||||
assert rc.json()["detail"] == "Data Download is already running."
|
||||
|
||||
# Reset running state
|
||||
ApiBG.download_data_running = False
|
||||
|
||||
# Test successful download
|
||||
mocker.patch(
|
||||
"freqtrade.data.history.history_utils.download_data",
|
||||
return_value=None,
|
||||
)
|
||||
|
||||
rc = client_post(client, f"{BASE_URI}/download_data", body)
|
||||
assert_response(rc)
|
||||
assert rc.json()["status"] == "Data Download started in background."
|
||||
job_id = rc.json()["job_id"]
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/background/{job_id}")
|
||||
assert_response(rc)
|
||||
response = rc.json()
|
||||
assert response["job_id"] == job_id
|
||||
assert response["job_category"] == "download_data"
|
||||
# Job finishes immediately due to mock.
|
||||
assert response["status"] == "success"
|
||||
|
||||
# Background list contains the job
|
||||
rc = client_get(client, f"{BASE_URI}/background")
|
||||
assert_response(rc)
|
||||
response = rc.json()
|
||||
assert isinstance(response, list)
|
||||
assert len(response) == 1
|
||||
assert response[0]["job_id"] == job_id
|
||||
|
||||
# Test error case
|
||||
ApiBG.download_data_running = False
|
||||
mocker.patch(
|
||||
"freqtrade.data.history.history_utils.download_data",
|
||||
side_effect=OperationalException("Download error"),
|
||||
)
|
||||
rc = client_post(client, f"{BASE_URI}/download_data", body)
|
||||
assert_response(rc)
|
||||
assert rc.json()["status"] == "Data Download started in background."
|
||||
job_id = rc.json()["job_id"]
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/background/{job_id}")
|
||||
assert_response(rc)
|
||||
response = rc.json()
|
||||
assert response["job_id"] == job_id
|
||||
assert response["job_category"] == "download_data"
|
||||
assert response["status"] == "failed"
|
||||
assert response["error"] == "Download error"
|
||||
|
||||
@@ -2355,8 +2355,8 @@ def test_send_msg_exit_notification(default_conf, mocker) -> None:
|
||||
"*Direction:* `Long`\n"
|
||||
"*Amount:* `1333.33333333`\n"
|
||||
"*Open Rate:* `0.00075 ETH`\n"
|
||||
"*Current Rate:* `0.00032 ETH`\n"
|
||||
"*Exit Rate:* `0.00032 ETH`\n"
|
||||
"*Current Rate:* `0.0003201 ETH`\n"
|
||||
"*Exit Rate:* `0.0003201 ETH`\n"
|
||||
"*Duration:* `1:00:00 (60.0 min)`"
|
||||
)
|
||||
|
||||
@@ -2398,8 +2398,8 @@ def test_send_msg_exit_notification(default_conf, mocker) -> None:
|
||||
"*Direction:* `Long`\n"
|
||||
"*Amount:* `1333.33333333`\n"
|
||||
"*Open Rate:* `0.00075 ETH`\n"
|
||||
"*Current Rate:* `0.00032 ETH`\n"
|
||||
"*Exit Rate:* `0.00032 ETH`\n"
|
||||
"*Current Rate:* `0.0003201 ETH`\n"
|
||||
"*Exit Rate:* `0.0003201 ETH`\n"
|
||||
"*Remaining:* `0.01 ETH / -24.812 USD`"
|
||||
)
|
||||
|
||||
@@ -2437,8 +2437,8 @@ def test_send_msg_exit_notification(default_conf, mocker) -> None:
|
||||
"*Direction:* `Long`\n"
|
||||
"*Amount:* `1333.33333333`\n"
|
||||
"*Open Rate:* `0.00075 ETH`\n"
|
||||
"*Current Rate:* `0.00032 ETH`\n"
|
||||
"*Exit Rate:* `0.00032 ETH`\n"
|
||||
"*Current Rate:* `0.0003201 ETH`\n"
|
||||
"*Exit Rate:* `0.0003201 ETH`\n"
|
||||
"*Duration:* `1 day, 2:30:00 (1590.0 min)`"
|
||||
)
|
||||
# Reset singleton function to avoid random breaks
|
||||
@@ -2536,7 +2536,7 @@ def test_send_msg_exit_fill_notification(
|
||||
f"{leverage_text}"
|
||||
"*Amount:* `1333.33333333`\n"
|
||||
"*Open Rate:* `0.00075 ETH`\n"
|
||||
"*Exit Rate:* `0.00032 ETH`\n"
|
||||
"*Exit Rate:* `0.0003201 ETH`\n"
|
||||
"*Duration:* `1 day, 2:30:00 (1590.0 min)`"
|
||||
)
|
||||
|
||||
@@ -2686,8 +2686,8 @@ def test_send_msg_exit_notification_no_fiat(
|
||||
f"{leverage_text}`\n"
|
||||
"*Amount:* `1333.33333333`\n"
|
||||
"*Open Rate:* `0.00075 ETH`\n"
|
||||
"*Current Rate:* `0.00032 ETH`\n"
|
||||
"*Exit Rate:* `0.00032 ETH`\n"
|
||||
"*Current Rate:* `0.0003201 ETH`\n"
|
||||
"*Exit Rate:* `0.0003201 ETH`\n"
|
||||
"*Duration:* `2:35:03 (155.1 min)`"
|
||||
)
|
||||
|
||||
@@ -2885,3 +2885,85 @@ async def test_telegram_list_custom_data(default_conf_usdt, update, ticker, fee,
|
||||
) in msg_mock.call_args_list[2][0][0]
|
||||
|
||||
msg_mock.reset_mock()
|
||||
|
||||
|
||||
def test_noficiation_settings(default_conf_usdt, mocker):
|
||||
(telegram, _, _) = get_telegram_testobject(mocker, default_conf_usdt)
|
||||
telegram._config["telegram"].update(
|
||||
{
|
||||
"notification_settings": {
|
||||
"status": "silent",
|
||||
"warning": "on",
|
||||
"startup": "off",
|
||||
"entry": "silent",
|
||||
"entry_fill": "on",
|
||||
"entry_cancel": "silent",
|
||||
"exit": {
|
||||
"roi": "silent",
|
||||
"emergency_exit": "on",
|
||||
"force_exit": "on",
|
||||
"exit_signal": "silent",
|
||||
"trailing_stop_loss": "on",
|
||||
"stop_loss": "on",
|
||||
"stoploss_on_exchange": "on",
|
||||
"custom_exit": "silent",
|
||||
"partial_exit": "off",
|
||||
},
|
||||
"exit_fill": {
|
||||
"roi": "silent",
|
||||
"partial_exit": "off",
|
||||
"*": "silent", # Default to silent
|
||||
},
|
||||
"exit_cancel": "on",
|
||||
"protection_trigger": "off",
|
||||
"protection_trigger_global": "on",
|
||||
"strategy_msg": "off",
|
||||
"show_candle": "off",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
loudness = telegram._message_loudness
|
||||
|
||||
assert loudness({"type": RPCMessageType.ENTRY, "exit_reason": ""}) == "silent"
|
||||
assert loudness({"type": RPCMessageType.ENTRY_FILL, "exit_reason": ""}) == "on"
|
||||
assert loudness({"type": RPCMessageType.EXIT, "exit_reason": ""}) == "on"
|
||||
# Default to silent due to "*" definition
|
||||
assert loudness({"type": RPCMessageType.EXIT_FILL, "exit_reason": ""}) == "silent"
|
||||
assert loudness({"type": RPCMessageType.PROTECTION_TRIGGER, "exit_reason": ""}) == "off"
|
||||
assert loudness({"type": RPCMessageType.EXIT, "exit_reason": "roi"}) == "silent"
|
||||
assert loudness({"type": RPCMessageType.EXIT, "exit_reason": "partial_exit"}) == "off"
|
||||
# Not given key defaults to on
|
||||
assert loudness({"type": RPCMessageType.EXIT, "exit_reason": "cust_exit112"}) == "on"
|
||||
|
||||
assert loudness({"type": RPCMessageType.EXIT_FILL, "exit_reason": "roi"}) == "silent"
|
||||
assert loudness({"type": RPCMessageType.EXIT_FILL, "exit_reason": "partial_exit"}) == "off"
|
||||
# Default to silent due to "*" definition
|
||||
assert loudness({"type": RPCMessageType.EXIT_FILL, "exit_reason": "cust_exit112"}) == "silent"
|
||||
|
||||
# Simplified setup for exit
|
||||
telegram._config["telegram"].update(
|
||||
{
|
||||
"notification_settings": {
|
||||
"status": "silent",
|
||||
"warning": "on",
|
||||
"startup": "off",
|
||||
"entry": "silent",
|
||||
"entry_fill": "on",
|
||||
"entry_cancel": "silent",
|
||||
"exit": "off",
|
||||
"exit_cancel": "on",
|
||||
"exit_fill": "on",
|
||||
"protection_trigger": "off",
|
||||
"protection_trigger_global": "on",
|
||||
"strategy_msg": "off",
|
||||
"show_candle": "off",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
assert loudness({"type": RPCMessageType.EXIT_FILL, "exit_reason": "roi"}) == "on"
|
||||
# All regular exits are off
|
||||
assert loudness({"type": RPCMessageType.EXIT, "exit_reason": "roi"}) == "off"
|
||||
assert loudness({"type": RPCMessageType.EXIT, "exit_reason": "partial_exit"}) == "off"
|
||||
assert loudness({"type": RPCMessageType.EXIT, "exit_reason": "cust_exit112"}) == "off"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
@@ -175,7 +174,7 @@ class StrategyTestV3(IStrategy):
|
||||
current_rate: float,
|
||||
proposed_leverage: float,
|
||||
max_leverage: float,
|
||||
entry_tag: Optional[str],
|
||||
entry_tag: str | None,
|
||||
side: str,
|
||||
**kwargs,
|
||||
) -> float:
|
||||
@@ -190,14 +189,14 @@ class StrategyTestV3(IStrategy):
|
||||
current_time: datetime,
|
||||
current_rate: float,
|
||||
current_profit: float,
|
||||
min_stake: Optional[float],
|
||||
min_stake: float | None,
|
||||
max_stake: float,
|
||||
current_entry_rate: float,
|
||||
current_exit_rate: float,
|
||||
current_entry_profit: float,
|
||||
current_exit_profit: float,
|
||||
**kwargs,
|
||||
) -> Optional[float]:
|
||||
) -> float | None:
|
||||
if current_profit < -0.0075:
|
||||
orders = trade.select_filled_orders(trade.entry_side)
|
||||
return round(orders[0].stake_amount, 0)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pandas import DataFrame
|
||||
from strategy_test_v3 import StrategyTestV3
|
||||
@@ -34,10 +33,10 @@ class StrategyTestV3CustomEntryPrice(StrategyTestV3):
|
||||
def custom_entry_price(
|
||||
self,
|
||||
pair: str,
|
||||
trade: Optional[Trade],
|
||||
trade: Trade | None,
|
||||
current_time: datetime,
|
||||
proposed_rate: float,
|
||||
entry_tag: Optional[str],
|
||||
entry_tag: str | None,
|
||||
side: str,
|
||||
**kwargs,
|
||||
) -> float:
|
||||
|
||||
@@ -27,15 +27,8 @@ from freqtrade.strategy.parameters import (
|
||||
IntParameter,
|
||||
RealParameter,
|
||||
)
|
||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||
from freqtrade.util import dt_now
|
||||
from tests.conftest import (
|
||||
CURRENT_TEST_STRATEGY,
|
||||
TRADE_SIDES,
|
||||
create_mock_trades,
|
||||
log_has,
|
||||
log_has_re,
|
||||
)
|
||||
from tests.conftest import CURRENT_TEST_STRATEGY, TRADE_SIDES, log_has, log_has_re
|
||||
|
||||
from .strats.strategy_test_v3 import StrategyTestV3
|
||||
|
||||
@@ -900,68 +893,6 @@ def test_is_informative_pairs_callback(default_conf):
|
||||
assert [] == strategy.gather_informative_pairs()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"error",
|
||||
[
|
||||
ValueError,
|
||||
KeyError,
|
||||
Exception,
|
||||
],
|
||||
)
|
||||
def test_strategy_safe_wrapper_error(caplog, error):
|
||||
def failing_method():
|
||||
raise error("This is an error.")
|
||||
|
||||
with pytest.raises(StrategyError, match=r"This is an error."):
|
||||
strategy_safe_wrapper(failing_method, message="DeadBeef")()
|
||||
|
||||
assert log_has_re(r"DeadBeef.*", caplog)
|
||||
ret = strategy_safe_wrapper(failing_method, message="DeadBeef", default_retval=True)()
|
||||
|
||||
assert isinstance(ret, bool)
|
||||
assert ret
|
||||
|
||||
caplog.clear()
|
||||
# Test suppressing error
|
||||
ret = strategy_safe_wrapper(failing_method, message="DeadBeef", supress_error=True)()
|
||||
assert log_has_re(r"DeadBeef.*", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value", [1, 22, 55, True, False, {"a": 1, "b": "112"}, [1, 2, 3, 4], (4, 2, 3, 6)]
|
||||
)
|
||||
def test_strategy_safe_wrapper(value):
|
||||
def working_method(argumentpassedin):
|
||||
return argumentpassedin
|
||||
|
||||
ret = strategy_safe_wrapper(working_method, message="DeadBeef")(value)
|
||||
|
||||
assert isinstance(ret, type(value))
|
||||
assert ret == value
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_strategy_safe_wrapper_trade_copy(fee):
|
||||
create_mock_trades(fee)
|
||||
|
||||
def working_method(trade):
|
||||
assert len(trade.orders) > 0
|
||||
assert trade.orders
|
||||
trade.orders = []
|
||||
assert len(trade.orders) == 0
|
||||
return trade
|
||||
|
||||
trade = Trade.get_open_trades()[0]
|
||||
# Don't assert anything before strategy_wrapper.
|
||||
# This ensures that relationship loading works correctly.
|
||||
ret = strategy_safe_wrapper(working_method, message="DeadBeef")(trade=trade)
|
||||
assert isinstance(ret, Trade)
|
||||
assert id(trade) != id(ret)
|
||||
# Did not modify the original order
|
||||
assert len(trade.orders) > 0
|
||||
assert len(ret.orders) == 0
|
||||
|
||||
|
||||
def test_hyperopt_parameters():
|
||||
HyperoptStateContainer.set_state(HyperoptState.INDICATORS)
|
||||
from skopt.space import Categorical, Integer, Real
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import pytest
|
||||
|
||||
from freqtrade.exceptions import StrategyError
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||
from tests.conftest import create_mock_trades, log_has_re
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"error",
|
||||
[
|
||||
ValueError,
|
||||
KeyError,
|
||||
Exception,
|
||||
],
|
||||
)
|
||||
def test_strategy_safe_wrapper_error(caplog, error):
|
||||
def failing_method():
|
||||
raise error("This is an error.")
|
||||
|
||||
with pytest.raises(StrategyError, match=r"This is an error."):
|
||||
strategy_safe_wrapper(failing_method, message="DeadBeef")()
|
||||
|
||||
assert log_has_re(r"DeadBeef.*", caplog)
|
||||
ret = strategy_safe_wrapper(failing_method, message="DeadBeef", default_retval=True)()
|
||||
|
||||
assert isinstance(ret, bool)
|
||||
assert ret
|
||||
|
||||
caplog.clear()
|
||||
# Test suppressing error
|
||||
ret = strategy_safe_wrapper(failing_method, message="DeadBeef", supress_error=True)()
|
||||
assert log_has_re(r"DeadBeef.*", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value", [1, 22, 55, True, False, {"a": 1, "b": "112"}, [1, 2, 3, 4], (4, 2, 3, 6)]
|
||||
)
|
||||
def test_strategy_safe_wrapper(value):
|
||||
def working_method(argumentpassedin):
|
||||
return argumentpassedin
|
||||
|
||||
ret = strategy_safe_wrapper(working_method, message="DeadBeef")(value)
|
||||
|
||||
assert isinstance(ret, type(value))
|
||||
assert ret == value
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_strategy_safe_wrapper_trade_copy(fee):
|
||||
create_mock_trades(fee)
|
||||
trade_ = Trade.get_open_trades()[0]
|
||||
|
||||
def working_method(trade):
|
||||
assert len(trade.orders) > 0
|
||||
assert trade.orders
|
||||
trade.orders = []
|
||||
assert len(trade.orders) == 0
|
||||
assert id(trade_) != id(trade)
|
||||
return trade
|
||||
|
||||
# Don't assert anything before strategy_wrapper.
|
||||
# This ensures that relationship loading works correctly.
|
||||
ret = strategy_safe_wrapper(working_method, message="DeadBeef")(trade=trade_)
|
||||
assert isinstance(ret, Trade)
|
||||
assert id(trade_) != id(ret)
|
||||
# Did not modify the original order
|
||||
assert len(trade_.orders) > 0
|
||||
assert len(ret.orders) == 0
|
||||
@@ -489,7 +489,6 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog, tmp_pa
|
||||
"--timeframe",
|
||||
"1m",
|
||||
"--enable-position-stacking",
|
||||
"--disable-max-market-positions",
|
||||
"--timerange",
|
||||
":100",
|
||||
"--export",
|
||||
@@ -518,10 +517,6 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog, tmp_pa
|
||||
assert "position_stacking" in config
|
||||
assert log_has("Parameter --enable-position-stacking detected ...", caplog)
|
||||
|
||||
assert "use_max_market_positions" in config
|
||||
assert log_has("Parameter --disable-max-market-positions detected ...", caplog)
|
||||
assert log_has("max_open_trades set to unlimited ...", caplog)
|
||||
|
||||
assert "timerange" in config
|
||||
assert log_has("Parameter --timerange detected: {} ...".format(config["timerange"]), caplog)
|
||||
|
||||
@@ -570,8 +565,6 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non
|
||||
|
||||
assert "position_stacking" not in config
|
||||
|
||||
assert "use_max_market_positions" not in config
|
||||
|
||||
assert "timerange" not in config
|
||||
|
||||
assert "export" in config
|
||||
|
||||
+24
-1
@@ -86,7 +86,7 @@ def test_set_loggers_Filehandler(tmp_path):
|
||||
logger = logging.getLogger()
|
||||
orig_handlers = logger.handlers
|
||||
logger.handlers = []
|
||||
logfile = tmp_path / "ft_logfile.log"
|
||||
logfile = tmp_path / "logs/ft_logfile.log"
|
||||
config = {
|
||||
"verbosity": 2,
|
||||
"logfile": str(logfile),
|
||||
@@ -107,6 +107,29 @@ def test_set_loggers_Filehandler(tmp_path):
|
||||
logger.handlers = orig_handlers
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
|
||||
def test_set_loggers_Filehandler_without_permission(tmp_path):
|
||||
logger = logging.getLogger()
|
||||
orig_handlers = logger.handlers
|
||||
logger.handlers = []
|
||||
|
||||
try:
|
||||
tmp_path.chmod(0o400)
|
||||
logfile = tmp_path / "logs/ft_logfile.log"
|
||||
config = {
|
||||
"verbosity": 2,
|
||||
"logfile": str(logfile),
|
||||
}
|
||||
|
||||
setup_logging_pre()
|
||||
with pytest.raises(OperationalException):
|
||||
setup_logging(config)
|
||||
|
||||
logger.handlers = orig_handlers
|
||||
finally:
|
||||
tmp_path.chmod(0o700)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="systemd is not installed on every system, so we're not testing this.")
|
||||
def test_set_loggers_journald(mocker):
|
||||
logger = logging.getLogger()
|
||||
|
||||
@@ -168,7 +168,7 @@ def test_get_trade_stake_amount_unlimited_amount(
|
||||
assert result == 0
|
||||
|
||||
freqtrade.config["dry_run_wallet"] = 200
|
||||
freqtrade.wallets.start_cap = 200
|
||||
freqtrade.wallets._start_cap = 200
|
||||
result = freqtrade.wallets.get_trade_stake_amount("XRP/USDT", 3)
|
||||
assert round(result, 4) == round(result2, 4)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from freqtrade.util import decimals_per_coin, fmt_coin, round_value
|
||||
from freqtrade.util.formatters import fmt_coin2
|
||||
|
||||
|
||||
def test_decimals_per_coin():
|
||||
@@ -25,6 +26,16 @@ def test_fmt_coin():
|
||||
assert fmt_coin(222.2, "USDT", False, True) == "222.200"
|
||||
|
||||
|
||||
def test_fmt_coin2():
|
||||
assert fmt_coin2(222.222222, "USDT") == "222.222222 USDT"
|
||||
assert fmt_coin2(222.2, "XRP", 3, keep_trailing_zeros=True) == "222.200 XRP"
|
||||
assert fmt_coin2(222.2, "USDT") == "222.2 USDT"
|
||||
assert fmt_coin2(222.12745, "EUR") == "222.12745 EUR"
|
||||
assert fmt_coin2(0.1274512123, "BTC") == "0.12745121 BTC"
|
||||
assert fmt_coin2(0.1274512123, "ETH") == "0.12745121 ETH"
|
||||
assert fmt_coin2(0.00001245, "PEPE") == "0.00001245 PEPE"
|
||||
|
||||
|
||||
def test_round_value():
|
||||
assert round_value(222.222222, 3) == "222.222"
|
||||
assert round_value(222.2, 3) == "222.2"
|
||||
|
||||
Reference in New Issue
Block a user