Merge branch 'develop' into feature/proceed-exit-while-open-order

This commit is contained in:
Axel-CH
2024-12-01 10:51:41 -04:00
219 changed files with 18735 additions and 7501 deletions
+68 -39
View File
@@ -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
View File
@@ -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 ...
"""
+4 -4
View File
@@ -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()
+29 -2
View File
@@ -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))
+5 -5
View File
@@ -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
View File
@@ -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)
+54
View File
@@ -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"
+25
View File
@@ -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
+11 -6
View File
@@ -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,
+417
View File
@@ -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
+4 -3
View File
@@ -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
View File
@@ -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.")
+15 -10
View File
@@ -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
+6 -5
View File
@@ -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,
)
+7 -7
View File
@@ -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):
+67
View File
@@ -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,
]
+128 -17
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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:
+31
View File
@@ -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")
+4 -6
View File
@@ -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",
+5 -3
View File
@@ -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
View File
@@ -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"
+91 -9
View File
@@ -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"
+3 -4
View File
@@ -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:
+1 -70
View File
@@ -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
-7
View File
@@ -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
View File
@@ -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()
+1 -1
View File
@@ -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)
+11
View File
@@ -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"