Merge branch 'develop' into optuna

This commit is contained in:
Matthias
2025-05-08 19:25:33 +02:00
156 changed files with 12489 additions and 5844 deletions

View File

@@ -709,18 +709,38 @@ def test_download_and_install_ui(mocker, tmp_path):
def test_get_ui_download_url(mocker):
response = MagicMock()
response.json = MagicMock(
side_effect=[
[{"assets_url": "http://whatever.json", "name": "0.0.1"}],
[{"browser_download_url": "http://download.zip"}],
]
)
responses = [
[
{
# Pre-release is ignored
"assets_url": "http://whatever.json",
"name": "0.0.2",
"created_at": "2024-02-01T00:00:00Z",
"prerelease": True,
},
{
"assets_url": "http://whatever.json",
"name": "0.0.1",
"created_at": "2024-01-01T00:00:00Z",
"prerelease": False,
},
],
[{"browser_download_url": "http://download.zip"}],
]
response.json = MagicMock(side_effect=responses)
get_mock = mocker.patch("freqtrade.commands.deploy_ui.requests.get", return_value=response)
x, last_version = get_ui_download_url()
x, last_version = get_ui_download_url(None, False)
assert get_mock.call_count == 2
assert last_version == "0.0.1"
assert x == "http://download.zip"
response.json = MagicMock(side_effect=responses)
get_mock.reset_mock()
x, last_version = get_ui_download_url(None, True)
assert get_mock.call_count == 2
assert last_version == "0.0.2"
assert x == "http://download.zip"
def test_get_ui_download_url_direct(mocker):
response = MagicMock()
@@ -729,29 +749,33 @@ def test_get_ui_download_url_direct(mocker):
{
"assets_url": "http://whatever.json",
"name": "0.0.2",
"created_at": "2024-02-01T00:00:00Z",
"prerelease": False,
"assets": [{"browser_download_url": "http://download22.zip"}],
},
{
"assets_url": "http://whatever.json",
"name": "0.0.1",
"created_at": "2024-01-01T00:00:00Z",
"prerelease": False,
"assets": [{"browser_download_url": "http://download1.zip"}],
},
]
)
get_mock = mocker.patch("freqtrade.commands.deploy_ui.requests.get", return_value=response)
x, last_version = get_ui_download_url()
x, last_version = get_ui_download_url(None, False)
assert get_mock.call_count == 1
assert last_version == "0.0.2"
assert x == "http://download22.zip"
get_mock.reset_mock()
response.json.reset_mock()
x, last_version = get_ui_download_url("0.0.1")
x, last_version = get_ui_download_url("0.0.1", False)
assert last_version == "0.0.1"
assert x == "http://download1.zip"
with pytest.raises(ValueError, match="UI-Version not found."):
x, last_version = get_ui_download_url("0.0.3")
x, last_version = get_ui_download_url("0.0.3", False)
def test_download_data_keyboardInterrupt(mocker, markets):

View File

@@ -1,4 +1,4 @@
import subprocess
import subprocess # noqa: S404, RUF100
import time
from tests.conftest import is_arm, is_mac

View File

@@ -48,7 +48,7 @@ from tests.conftest_trades_usdt import (
logging.getLogger("").setLevel(logging.INFO)
# Do not mask numpy errors as warnings that no one read, raise the exсeption
# Do not mask numpy errors as warnings that no one read, raise the exception
np.seterr(all="raise")
CURRENT_TEST_STRATEGY = "StrategyTestV3"
@@ -165,7 +165,7 @@ def generate_trades_history(n_rows, start_date: datetime | None = None, days=5):
)
df["date"] = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
df = df.sort_values("timestamp").reset_index(drop=True)
assert list(df.columns) == constants.DEFAULT_TRADES_COLUMNS + ["date"]
assert list(df.columns) == [*constants.DEFAULT_TRADES_COLUMNS, "date"]
return df

View File

@@ -34,7 +34,7 @@ def hyperopt_test_result():
"roi_p2": 0.12508730043628782,
"roi_p3": 0.27766427921605896,
"stoploss": -0.2562930402099556,
}, # noqa: E501
},
"params_details": {
"buy": {
"mfi-value": 15,
@@ -65,7 +65,7 @@ def hyperopt_test_result():
2139: 0,
},
"stoploss": {"stoploss": -0.2562930402099556},
}, # noqa: E501
},
"results_metrics": {
"total_trades": 2,
"trade_count_long": 2,
@@ -82,7 +82,7 @@ def hyperopt_test_result():
"holding_avg": timedelta(minutes=3930.0),
"stake_currency": "BTC",
"strategy_name": "SampleStrategy",
}, # noqa: E501
},
"results_explanation": " 2 trades. Avg profit -1.25%. Total profit -0.00125625 BTC ( -2.51Σ%). Avg duration 3930.0 min.", # noqa: E501
"total_profit": -0.00125625,
"current_epoch": 1,
@@ -118,7 +118,7 @@ def hyperopt_test_result():
"roi_p2": 0.055519840060645045,
"roi_p3": 0.3253712811342459,
"stoploss": -0.338070047333259,
}, # noqa: E501
},
"params_details": {
"buy": {
"mfi-value": 17,
@@ -130,7 +130,7 @@ def hyperopt_test_result():
"adx-enabled": True,
"rsi-enabled": True,
"trigger": "macd_cross_signal",
}, # noqa: E501
},
"sell": {
"sell-mfi-value": 96,
"sell-fastd-value": 68,
@@ -141,13 +141,13 @@ def hyperopt_test_result():
"sell-adx-enabled": True,
"sell-rsi-enabled": True,
"sell-trigger": "sell-sar_reversal",
}, # noqa: E501
},
"roi": {
0: 0.4449309386008759,
140: 0.11955965746663,
823: 0.06403981740598495,
1157: 0,
}, # noqa: E501
},
"stoploss": {"stoploss": -0.338070047333259},
},
"results_metrics": {
@@ -164,7 +164,7 @@ def hyperopt_test_result():
"max_drawdown_account": 0.23,
"max_drawdown_abs": -0.00125625,
"holding_avg": timedelta(minutes=1200.0),
}, # noqa: E501
},
"results_explanation": " 1 trades. Avg profit 0.12%. Total profit 0.00006185 BTC ( 0.12Σ%). Avg duration 1200.0 min.", # noqa: E501
"total_profit": 6.185e-05,
"current_epoch": 2,
@@ -200,7 +200,7 @@ def hyperopt_test_result():
"roi_p2": 0.1488819964638463,
"roi_p3": 0.4102801822104605,
"stoploss": -0.05394588767607611,
}, # noqa: E501
},
"params_details": {
"buy": {
"mfi-value": 25,
@@ -231,7 +231,7 @@ def hyperopt_test_result():
1685: 0,
},
"stoploss": {"stoploss": -0.05394588767607611},
}, # noqa: E501
},
"results_metrics": {
"total_trades": 621,
"trade_count_long": 621,
@@ -246,7 +246,7 @@ def hyperopt_test_result():
"max_drawdown_account": 0.25,
"max_drawdown_abs": -272.515306,
"holding_avg": timedelta(minutes=1691.207729468599),
}, # noqa: E501
},
"results_explanation": " 621 trades. Avg profit -0.44%. Total profit -0.13639474 BTC (-272.52Σ%). Avg duration 1691.2 min.", # noqa: E501
"total_profit": -0.13639474,
"current_epoch": 3,
@@ -282,7 +282,7 @@ def hyperopt_test_result():
"roi_p2": 0.14258587851894644,
"roi_p3": 0.20671291201040828,
"stoploss": -0.11818343570194478,
}, # noqa: E501
},
"params_details": {
"buy": {
"mfi-value": 13,
@@ -313,7 +313,7 @@ def hyperopt_test_result():
2293: 0,
},
"stoploss": {"stoploss": -0.11818343570194478},
}, # noqa: E501
},
"results_metrics": {
"total_trades": 0,
"trade_count_long": 0,
@@ -327,13 +327,13 @@ def hyperopt_test_result():
"max_drawdown_account": 0.0,
"max_drawdown_abs": 0.0,
"holding_avg": timedelta(),
}, # noqa: E501
},
"results_explanation": " 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.", # noqa: E501
"total_profit": 0,
"current_epoch": 4,
"is_initial_point": True,
"is_random": False,
"is_best": False, # noqa: E501
"is_best": False,
},
{
"loss": 0.22195522184191518,
@@ -363,7 +363,7 @@ def hyperopt_test_result():
"roi_p2": 0.08946698095898986,
"roi_p3": 0.1454876733325284,
"stoploss": -0.18181041180901014,
}, # noqa: E501
},
"params_details": {
"buy": {
"mfi-value": 17,
@@ -394,7 +394,7 @@ def hyperopt_test_result():
2314: 0,
},
"stoploss": {"stoploss": -0.18181041180901014},
}, # noqa: E501
},
"results_metrics": {
"total_trades": 14,
"trade_count_long": 14,
@@ -409,7 +409,7 @@ def hyperopt_test_result():
"max_drawdown_account": 0.34,
"max_drawdown_abs": -4.955321,
"holding_avg": timedelta(minutes=3402.8571428571427),
}, # noqa: E501
},
"results_explanation": " 14 trades. Avg profit -0.35%. Total profit -0.00248014 BTC ( -4.96Σ%). Avg duration 3402.9 min.", # noqa: E501
"total_profit": -0.002480140000000001,
"current_epoch": 5,
@@ -445,7 +445,7 @@ def hyperopt_test_result():
"roi_p2": 0.11659519602202795,
"roi_p3": 0.0953744132197762,
"stoploss": -0.024551752215582423,
}, # noqa: E501
},
"params_details": {
"buy": {
"mfi-value": 22,
@@ -476,7 +476,7 @@ def hyperopt_test_result():
1091: 0,
},
"stoploss": {"stoploss": -0.024551752215582423},
}, # noqa: E501
},
"results_metrics": {
"total_trades": 39,
"trade_count_long": 39,
@@ -491,7 +491,7 @@ def hyperopt_test_result():
"max_drawdown_account": 0.45,
"max_drawdown_abs": -4.955321,
"holding_avg": timedelta(minutes=636.9230769230769),
}, # noqa: E501
},
"results_explanation": " 39 trades. Avg profit -0.21%. Total profit -0.00417730 BTC ( -8.35Σ%). Avg duration 636.9 min.", # noqa: E501
"total_profit": -0.0041773,
"current_epoch": 6,
@@ -527,7 +527,7 @@ def hyperopt_test_result():
"roi_p2": 0.04984118697312542,
"roi_p3": 0.37521058680247044,
"stoploss": -0.14613268022709905,
}, # noqa: E501
},
"params_details": {
"buy": {
"mfi-value": 13,
@@ -556,9 +556,9 @@ def hyperopt_test_result():
145: 0.10853310701097472,
765: 0.0586919200378493,
1536: 0,
}, # noqa: E501
},
"stoploss": {"stoploss": -0.14613268022709905},
}, # noqa: E501
},
"results_metrics": {
"total_trades": 318,
"trade_count_long": 318,
@@ -573,7 +573,7 @@ def hyperopt_test_result():
"max_drawdown_account": 0.50,
"max_drawdown_abs": -200.955321,
"holding_avg": timedelta(minutes=3140.377358490566),
}, # noqa: E501
},
"results_explanation": " 318 trades. Avg profit -0.40%. Total profit -0.06339929 BTC (-126.67Σ%). Avg duration 3140.4 min.", # noqa: E501
"total_profit": -0.06339929,
"current_epoch": 7,
@@ -582,7 +582,7 @@ def hyperopt_test_result():
"is_best": False,
},
{
"loss": 20.0, # noqa: E501
"loss": 20.0,
"params_dict": {
"mfi-value": 24,
"fastd-value": 43,
@@ -609,7 +609,7 @@ def hyperopt_test_result():
"roi_p2": 0.0606240398618907,
"roi_p3": 0.1729012220156157,
"stoploss": -0.1588514289110401,
}, # noqa: E501
},
"params_details": {
"buy": {
"mfi-value": 24,
@@ -640,7 +640,7 @@ def hyperopt_test_result():
1813: 0,
},
"stoploss": {"stoploss": -0.1588514289110401},
}, # noqa: E501
},
"results_metrics": {
"total_trades": 1,
"trade_count_long": 1,
@@ -655,7 +655,7 @@ def hyperopt_test_result():
"max_drawdown_account": 0.0,
"max_drawdown_abs": 0.52,
"holding_avg": timedelta(minutes=5340.0),
}, # noqa: E501
},
"results_explanation": " 1 trades. Avg profit 0.00%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration 5340.0 min.", # noqa: E501
"total_profit": 0.0,
"current_epoch": 8,
@@ -691,7 +691,7 @@ def hyperopt_test_result():
"roi_p2": 0.10335480573205287,
"roi_p3": 0.10322347377503042,
"stoploss": -0.2780610808108503,
}, # noqa: E501
},
"params_details": {
"buy": {
"mfi-value": 22,
@@ -722,7 +722,7 @@ def hyperopt_test_result():
2018: 0,
},
"stoploss": {"stoploss": -0.2780610808108503},
}, # noqa: E501
},
"results_metrics": {
"total_trades": 229,
"trade_count_long": 229,
@@ -737,16 +737,16 @@ def hyperopt_test_result():
"max_drawdown_account": 0.41,
"max_drawdown_abs": -150.955321,
"holding_avg": timedelta(minutes=6505.676855895196),
}, # noqa: E501
},
"results_explanation": " 229 trades. Avg profit -0.38%. Total profit -0.04405007 BTC ( -88.01Σ%). Avg duration 6505.7 min.", # noqa: E501
"total_profit": -0.044050070000000004, # noqa: E501
"total_profit": -0.044050070000000004,
"current_epoch": 9,
"is_initial_point": True,
"is_random": False,
"is_best": False,
},
{
"loss": -0.2604606005845212, # noqa: E501
"loss": -0.2604606005845212,
"params_dict": {
"mfi-value": 23,
"fastd-value": 24,
@@ -773,7 +773,7 @@ def hyperopt_test_result():
"roi_p2": 0.09623192684243963,
"roi_p3": 0.04428219070850663,
"stoploss": -0.16992287161634415,
}, # noqa: E501
},
"params_details": {
"buy": {
"mfi-value": 23,
@@ -804,7 +804,7 @@ def hyperopt_test_result():
1471: 0,
},
"stoploss": {"stoploss": -0.16992287161634415},
}, # noqa: E501
},
"results_metrics": {
"total_trades": 4,
"trade_count_long": 4,
@@ -819,7 +819,7 @@ def hyperopt_test_result():
"max_drawdown_account": 0.13,
"max_drawdown_abs": -4.955321,
"holding_avg": timedelta(minutes=2850.0),
}, # noqa: E501
},
"results_explanation": " 4 trades. Avg profit 0.11%. Total profit 0.00021629 BTC ( 0.43Σ%). Avg duration 2850.0 min.", # noqa: E501
"total_profit": 0.00021629,
"current_epoch": 10,
@@ -828,7 +828,7 @@ def hyperopt_test_result():
"is_best": True,
},
{
"loss": 4.876465945994304, # noqa: E501
"loss": 4.876465945994304,
"params_dict": {
"mfi-value": 20,
"fastd-value": 32,
@@ -855,7 +855,7 @@ def hyperopt_test_result():
"roi_p2": 0.1352282078262871,
"roi_p3": 0.1913307406325751,
"stoploss": -0.25728526022513887,
}, # noqa: E501
},
"params_details": {
"buy": {
"mfi-value": 20,
@@ -886,7 +886,7 @@ def hyperopt_test_result():
1466: 0,
},
"stoploss": {"stoploss": -0.25728526022513887},
}, # noqa: E501
},
# New Hyperopt mode!
"results_metrics": {
"total_trades": 117,
@@ -902,7 +902,7 @@ def hyperopt_test_result():
"max_drawdown_account": 0.52,
"max_drawdown_abs": -224.955321,
"holding_avg": timedelta(minutes=4282.5641025641025),
}, # noqa: E501
},
"results_explanation": " 117 trades. Avg profit -1.27%. Total profit -0.07436117 BTC (-148.57Σ%). Avg duration 4282.6 min.", # noqa: E501
"total_profit": -0.07436117,
"current_epoch": 11,
@@ -938,7 +938,7 @@ def hyperopt_test_result():
"roi_p2": 0.12473718444931989,
"roi_p3": 0.2896360635226823,
"stoploss": -0.30889015124682806,
}, # noqa: E501
},
"params_details": {
"buy": {
"mfi-value": 10,
@@ -969,7 +969,7 @@ def hyperopt_test_result():
2145: 0,
},
"stoploss": {"stoploss": -0.30889015124682806},
}, # noqa: E501
},
"results_metrics": {
"total_trades": 0,
"trade_count_long": 0,
@@ -984,7 +984,7 @@ def hyperopt_test_result():
"max_drawdown_account": 0.0,
"max_drawdown_abs": 0.0,
"holding_avg": timedelta(),
}, # noqa: E501
},
"results_explanation": " 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.", # noqa: E501
"total_profit": 0,
"current_epoch": 12,

View File

@@ -56,7 +56,7 @@ def test_get_latest_backtest_filename(testdatadir, mocker):
res = get_latest_backtest_filename(str(testdir_bt))
assert res == "backtest-result.json"
mocker.patch("freqtrade.data.btanalysis.json_load", return_value={})
mocker.patch("freqtrade.data.btanalysis.bt_fileutils.json_load", return_value={})
with pytest.raises(ValueError, match=r"Invalid '.last_result.json' format."):
get_latest_backtest_filename(testdir_bt)
@@ -84,8 +84,8 @@ def test_load_backtest_metadata(mocker, testdatadir):
res = load_backtest_metadata(testdatadir / "nonexistent.file.json")
assert res == {}
mocker.patch("freqtrade.data.btanalysis.get_backtest_metadata_filename")
mocker.patch("freqtrade.data.btanalysis.json_load", side_effect=Exception())
mocker.patch("freqtrade.data.btanalysis.bt_fileutils.get_backtest_metadata_filename")
mocker.patch("freqtrade.data.btanalysis.bt_fileutils.json_load", side_effect=Exception())
with pytest.raises(
OperationalException, match=r"Unexpected error.*loading backtest metadata\."
):
@@ -94,7 +94,7 @@ def test_load_backtest_metadata(mocker, testdatadir):
def test_load_backtest_data_old_format(testdatadir, mocker):
filename = testdatadir / "backtest-result_test222.json"
mocker.patch("freqtrade.data.btanalysis.load_backtest_stats", return_value=[])
mocker.patch("freqtrade.data.btanalysis.bt_fileutils.load_backtest_stats", return_value=[])
with pytest.raises(
OperationalException,
@@ -149,7 +149,7 @@ def test_load_backtest_data_multi(testdatadir):
def test_load_trades_from_db(default_conf, fee, is_short, mocker):
create_mock_trades(fee, is_short)
# remove init so it does not init again
init_mock = mocker.patch("freqtrade.data.btanalysis.init_db", MagicMock())
init_mock = mocker.patch("freqtrade.data.btanalysis.bt_fileutils.init_db", MagicMock())
trades = load_trades_from_db(db_url=default_conf["db_url"])
assert init_mock.call_count == 1
@@ -221,8 +221,10 @@ def test_analyze_trade_parallelism(testdatadir):
def test_load_trades(default_conf, mocker):
db_mock = mocker.patch("freqtrade.data.btanalysis.load_trades_from_db", MagicMock())
bt_mock = mocker.patch("freqtrade.data.btanalysis.load_backtest_data", MagicMock())
db_mock = mocker.patch(
"freqtrade.data.btanalysis.bt_fileutils.load_trades_from_db", MagicMock()
)
bt_mock = mocker.patch("freqtrade.data.btanalysis.bt_fileutils.load_backtest_data", MagicMock())
load_trades(
"DB",
@@ -268,6 +270,14 @@ def test_calculate_market_change(testdatadir):
assert isinstance(result, float)
assert pytest.approx(result) == 0.01100002
result = calculate_market_change(data, min_date=dt_utc(2018, 1, 20))
assert isinstance(result, float)
assert pytest.approx(result) == 0.0375149
# Move min-date after the last date
result = calculate_market_change(data, min_date=dt_utc(2018, 2, 20))
assert pytest.approx(result) == 0.0
def test_combine_dataframes_with_mean(testdatadir):
pairs = ["ETH/BTC", "ADA/BTC"]
@@ -562,14 +572,15 @@ def test_calculate_max_drawdown2():
assert pytest.approx(drawdown.relative_account_drawdown) == 0.32129575
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")
# No losing trade ...
drawdown = calculate_max_drawdown(df, date_col="open_date", value_col="profit")
assert drawdown.drawdown_abs == 0.0
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")
assert drawdown.drawdown_abs == 0.043965
assert drawdown.drawdown_abs == 0.055545
@pytest.mark.parametrize(

View File

@@ -139,7 +139,7 @@ def test_backtest_analysis_on_entry_and_rejected_signals_nomock(
# test group 0 and indicator list
args = get_args(
base_args + ["--analysis-groups", "0", "--indicator-list", "close", "rsi", "profit_abs"]
[*base_args, "--analysis-groups", "0", "--indicator-list", "close", "rsi", "profit_abs"]
)
start_analysis_entries_exits(args)
captured = capsys.readouterr()
@@ -172,7 +172,7 @@ def test_backtest_analysis_on_entry_and_rejected_signals_nomock(
assert "profit_abs" in captured.out
# test group 1
args = get_args(base_args + ["--analysis-groups", "1"])
args = get_args([*base_args, "--analysis-groups", "1"])
start_analysis_entries_exits(args)
captured = capsys.readouterr()
assert "enter_tag_long_a" in captured.out
@@ -185,7 +185,7 @@ def test_backtest_analysis_on_entry_and_rejected_signals_nomock(
assert "0" in captured.out
# test group 2
args = get_args(base_args + ["--analysis-groups", "2"])
args = get_args([*base_args, "--analysis-groups", "2"])
start_analysis_entries_exits(args)
captured = capsys.readouterr()
assert "enter_tag_long_a" in captured.out
@@ -200,7 +200,7 @@ def test_backtest_analysis_on_entry_and_rejected_signals_nomock(
assert "2.5" in captured.out
# test group 3
args = get_args(base_args + ["--analysis-groups", "3"])
args = get_args([*base_args, "--analysis-groups", "3"])
start_analysis_entries_exits(args)
captured = capsys.readouterr()
assert "LTC/BTC" in captured.out
@@ -215,7 +215,7 @@ def test_backtest_analysis_on_entry_and_rejected_signals_nomock(
assert "2" in captured.out
# test group 4
args = get_args(base_args + ["--analysis-groups", "4"])
args = get_args([*base_args, "--analysis-groups", "4"])
start_analysis_entries_exits(args)
captured = capsys.readouterr()
assert "LTC/BTC" in captured.out
@@ -235,7 +235,7 @@ def test_backtest_analysis_on_entry_and_rejected_signals_nomock(
assert "2.5" in captured.out
# test group 5
args = get_args(base_args + ["--analysis-groups", "5"])
args = get_args([*base_args, "--analysis-groups", "5"])
start_analysis_entries_exits(args)
captured = capsys.readouterr()
assert "exit_signal" in captured.out
@@ -245,7 +245,7 @@ def test_backtest_analysis_on_entry_and_rejected_signals_nomock(
# test date filtering
args = get_args(
base_args + ["--analysis-groups", "0", "1", "2", "--timerange", "20180129-20180130"]
[*base_args, "--analysis-groups", "0", "1", "2", "--timerange", "20180129-20180130"]
)
start_analysis_entries_exits(args)
captured = capsys.readouterr()
@@ -253,7 +253,7 @@ def test_backtest_analysis_on_entry_and_rejected_signals_nomock(
assert "enter_tag_long_b" not in captured.out
# Due to the backtest mock, there's no rejected signals generated.
args = get_args(base_args + ["--rejected-signals"])
args = get_args([*base_args, "--rejected-signals"])
start_analysis_entries_exits(args)
captured = capsys.readouterr()
assert "no rejected signals" in captured.out
@@ -379,8 +379,8 @@ def test_backtest_analysis_with_invalid_config(
# test with both entry and exit only arguments
args = get_args(
base_args
+ [
[
*base_args,
"--analysis-groups",
"0",
"--indicator-list",
@@ -518,8 +518,8 @@ def test_backtest_analysis_on_entry_and_rejected_signals_only_entry_signals(
# test group 0 and indicator list
args = get_args(
base_args
+ [
[
*base_args,
"--analysis-groups",
"0",
"--indicator-list",

View File

@@ -299,7 +299,7 @@ def test_liquidation_price_binance(
def get_maint_ratio(pair_, stake_amount):
if pair_ != pair:
oc = [c for c in open_trades if c["pair"] == pair_][0]
oc = next(c for c in open_trades if c["pair"] == pair_)
return oc["mm_ratio"], oc["maintenance_amt"]
return mm_ratio, maintenance_amt

View File

@@ -2838,6 +2838,11 @@ def test_get_next_limit_in_list():
assert Exchange.get_next_limit_in_list(21, None) == 21
assert Exchange.get_next_limit_in_list(100, None) == 100
assert Exchange.get_next_limit_in_list(1000, None) == 1000
# With upper limit
assert Exchange.get_next_limit_in_list(1000, None, upper_limit=None) == 1000
assert Exchange.get_next_limit_in_list(1000, None, upper_limit=500) == 500
# with upper limit and range, limit_range wins
assert Exchange.get_next_limit_in_list(1000, limit_range, upper_limit=500) == 1000
@pytest.mark.parametrize("exchange_name", EXCHANGES)
@@ -5594,11 +5599,13 @@ def test_liquidation_price_is_none(
def test_get_max_pair_stake_amount(
mocker,
default_conf,
leverage_tiers,
):
api_mock = MagicMock()
default_conf["margin_mode"] = "isolated"
default_conf["trading_mode"] = "futures"
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange._leverage_tiers = leverage_tiers
markets = {
"XRP/USDT:USDT": {
"limits": {
@@ -5662,11 +5669,23 @@ def test_get_max_pair_stake_amount(
"contractSize": 0.01,
"spot": False,
},
"ZEC/USDT:USDT": {
"limits": {
"amount": {"min": 0.001, "max": None},
"cost": {"min": 5, "max": None},
},
"contractSize": 1,
"spot": False,
},
}
mocker.patch(f"{EXMS}.markets", markets)
assert exchange.get_max_pair_stake_amount("XRP/USDT:USDT", 2.0) == 20000
assert exchange.get_max_pair_stake_amount("XRP/USDT:USDT", 2.0, 5) == 4000
# limit leverage tiers
assert exchange.get_max_pair_stake_amount("ZEC/USDT:USDT", 2.0, 5) == 100_000
assert exchange.get_max_pair_stake_amount("ZEC/USDT:USDT", 2.0, 50) == 1000
assert exchange.get_max_pair_stake_amount("LTC/USDT:USDT", 2.0) == float("inf")
assert exchange.get_max_pair_stake_amount("ETH/USDT:USDT", 2.0) == 200
assert exchange.get_max_pair_stake_amount("DOGE/USDT:USDT", 2.0) == 500
@@ -5897,8 +5916,8 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers):
assert exchange.get_max_leverage("XRP/USDT:USDT", 1.0) == 20.0
assert exchange.get_max_leverage("BNB/USDT:USDT", 100.0) == 75.0
assert exchange.get_max_leverage("BTC/USDT:USDT", 170.30) == 125.0
assert pytest.approx(exchange.get_max_leverage("XRP/USDT:USDT", 99999.9)) == 5.000005
assert pytest.approx(exchange.get_max_leverage("BNB/USDT:USDT", 1500)) == 33.333333333333333
assert pytest.approx(exchange.get_max_leverage("XRP/USDT:USDT", 99999.9)) == 5
assert pytest.approx(exchange.get_max_leverage("BNB/USDT:USDT", 1500)) == 25
assert exchange.get_max_leverage("BTC/USDT:USDT", 300000000) == 2.0
assert exchange.get_max_leverage("BTC/USDT:USDT", 600000000) == 1.0 # Last tier

View File

@@ -71,7 +71,7 @@ def test_get_balances_prod_kraken(default_conf, mocker):
"4TH": balance_item.copy(),
"EUR": balance_item.copy(),
"BTC": {"free": 0.0, "total": 0.0, "used": 0.0},
"XBT.F": balance_item.copy(),
"BTC.F": balance_item.copy(),
"timestamp": 123123,
}
)

View File

@@ -156,7 +156,7 @@ EXCHANGES = {
"ADA.F": {"free": 2.0, "total": 2.0, "used": 0.0},
"BTC": {"free": 0.0006, "total": 0.0006, "used": 0.0},
# XBT.F should be mapped to BTC.F
"XBT.F": {"free": 0.001, "total": 0.001, "used": 0.0},
"BTC.F": {"free": 0.001, "total": 0.001, "used": 0.0},
},
},
},

View File

@@ -3,6 +3,7 @@ import time
from datetime import timedelta
from unittest.mock import MagicMock, PropertyMock
import pytest
import time_machine
from freqtrade.data.dataprovider import DataProvider
@@ -38,6 +39,25 @@ def test_worker_running(mocker, default_conf, caplog) -> None:
assert isinstance(worker.freqtrade.strategy.dp, DataProvider)
def test_worker_paused(mocker, default_conf, caplog) -> None:
mock_throttle = MagicMock()
mocker.patch("freqtrade.worker.Worker._throttle", mock_throttle)
mocker.patch("freqtrade.persistence.Trade.stoploss_reinitialization", MagicMock())
worker = get_patched_worker(mocker, default_conf)
worker.freqtrade.state = State.PAUSED
state = worker._worker(old_state=State.RUNNING)
assert state is State.PAUSED
assert log_has("Changing state from RUNNING to: PAUSED", caplog)
assert mock_throttle.call_count == 1
# Check strategy is loaded, and received a dataprovider object
assert worker.freqtrade.strategy
assert worker.freqtrade.strategy.dp
assert isinstance(worker.freqtrade.strategy.dp, DataProvider)
def test_worker_stopped(mocker, default_conf, caplog) -> None:
mock_throttle = MagicMock()
mocker.patch("freqtrade.worker.Worker._throttle", mock_throttle)
@@ -50,6 +70,54 @@ def test_worker_stopped(mocker, default_conf, caplog) -> None:
assert mock_throttle.call_count == 1
@pytest.mark.parametrize(
"old_state,target_state,startup_call,log_fragment",
[
(State.STOPPED, State.PAUSED, True, "Changing state from STOPPED to: PAUSED"),
(State.RUNNING, State.PAUSED, False, "Changing state from RUNNING to: PAUSED"),
(State.PAUSED, State.RUNNING, False, "Changing state from PAUSED to: RUNNING"),
(State.PAUSED, State.STOPPED, False, "Changing state from PAUSED to: STOPPED"),
(State.RELOAD_CONFIG, State.RUNNING, True, "Changing state from RELOAD_CONFIG to: RUNNING"),
(
State.RELOAD_CONFIG,
State.STOPPED,
False,
"Changing state from RELOAD_CONFIG to: STOPPED",
),
],
)
def test_worker_lifecycle(
mocker,
default_conf,
caplog,
old_state,
target_state,
startup_call,
log_fragment,
):
mock_throttle = mocker.MagicMock()
mocker.patch("freqtrade.worker.Worker._throttle", mock_throttle)
mocker.patch("freqtrade.persistence.Trade.stoploss_reinitialization")
startup = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.startup")
worker = get_patched_worker(mocker, default_conf)
worker.freqtrade.state = target_state
new_state = worker._worker(old_state=old_state)
assert new_state is target_state
assert log_has(log_fragment, caplog)
assert mock_throttle.call_count == 1
assert startup.call_count == (1 if startup_call else 0)
# For any state where the strategy should be initialized
if target_state in (State.RUNNING, State.PAUSED):
assert worker.freqtrade.strategy
assert isinstance(worker.freqtrade.strategy.dp, DataProvider)
else:
assert new_state is State.STOPPED
def test_throttle(mocker, default_conf, caplog) -> None:
def throttled_func():
return 42

View File

@@ -69,8 +69,8 @@ def _build_backtest_dataframe(data):
]
if len(data[0]) == 8:
# No short columns
data = [d + [0, 0] for d in data]
columns = columns + ["enter_tag"] if len(data[0]) == 11 else columns
data = [[*d, 0, 0] for d in data]
columns = [*columns, "enter_tag"] if len(data[0]) == 11 else columns
frame = DataFrame.from_records(data, columns=columns)
frame["date"] = frame["date"].apply(_get_frame_time_from_offset)

View File

@@ -102,7 +102,7 @@ tc3 = BTContainer(
)
# Test 4: Minus 3% / recovery +15%
# Candle Data for test 3 Candle drops 3% Closed 15% up
# Candle Data for test 3 - Candle drops 3% Closed 15% up
# Test with Stop-loss at 2% ROI 6%
# Stop-Loss Triggered 2% Loss
tc4 = BTContainer(

View File

@@ -828,6 +828,7 @@ def test_backtest_one(default_conf, mocker, testdatadir) -> None:
},
],
],
"funding_fees": [0.0, 0.0],
}
)
pd.testing.assert_frame_equal(results, expected)
@@ -991,7 +992,7 @@ def test_backtest_one_detail_futures(
timerange=timerange,
candle_type=CandleType.FUTURES,
)
backtesting.load_bt_data_detail()
backtesting._load_bt_data_detail()
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
@@ -1119,7 +1120,7 @@ def test_backtest_one_detail_futures_funding_fees(
timerange=timerange,
candle_type=CandleType.FUTURES,
)
backtesting.load_bt_data_detail()
backtesting._load_bt_data_detail()
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
@@ -2576,7 +2577,7 @@ def test_backtest_start_multi_strat_caching(
],
)
mocker.patch.multiple(
"freqtrade.data.btanalysis",
"freqtrade.data.btanalysis.bt_fileutils",
load_backtest_metadata=load_backtest_metadata,
load_backtest_stats=load_backtest_stats,
)

View File

@@ -80,6 +80,7 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
"is_short": [False, False],
"open_timestamp": [1517251200000, 1517283000000],
"close_timestamp": [1517263200000, 1517285400000],
"funding_fees": [0.0, 0.0],
}
)
results_no = results.drop(columns=["orders"])

View File

@@ -685,7 +685,7 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
'{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi"'
':{},"stoploss":null,"trailing_stop":null,"max_open_trades":null}'
)
assert result_str in out # noqa: E501
assert result_str in out
# Should be called for historical candle data
assert dumper.call_count == 1
assert dumper2.call_count == 1
@@ -742,7 +742,7 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
assert (
'{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi":{},"stoploss":null}'
in out
) # noqa: E501
)
# Should be called for historical candle data
assert dumper.call_count == 1
assert dumper2.call_count == 1

View File

@@ -153,6 +153,7 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) ->
"SharpeHyperOptLossDaily",
"MaxDrawDownHyperOptLoss",
"MaxDrawDownRelativeHyperOptLoss",
"MaxDrawDownPerPairHyperOptLoss",
"CalmarHyperOptLoss",
"ProfitDrawDownHyperOptLoss",
"MultiMetricHyperOptLoss",
@@ -165,6 +166,34 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct
results_under = hyperopt_results.copy()
results_under["profit_abs"] = hyperopt_results["profit_abs"] / 2 - 0.2
results_under["profit_ratio"] = hyperopt_results["profit_ratio"] / 2
pair_results = [
{
"key": "ETH/USDT",
"max_drawdown_abs": 50.0,
"profit_total_abs": 100.0,
},
{
"key": "BTC/USDT",
"max_drawdown_abs": 50.0,
"profit_total_abs": 100.0,
},
]
pair_results_over = [
{
**p,
"max_drawdown_abs": p["max_drawdown_abs"] * 0.5,
"profit_total_abs": p["profit_total_abs"] * 2,
}
for p in pair_results
]
pair_results_under = [
{
**p,
"max_drawdown_abs": p["max_drawdown_abs"] * 2,
"profit_total_abs": p["profit_total_abs"] * 0.5,
}
for p in pair_results
]
default_conf.update({"hyperopt_loss": lossfunction})
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
@@ -175,7 +204,10 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct
max_date=datetime(2019, 5, 1),
config=default_conf,
processed=None,
backtest_stats={"profit_total": hyperopt_results["profit_abs"].sum()},
backtest_stats={
"profit_total": hyperopt_results["profit_abs"].sum(),
"results_per_pair": pair_results,
},
starting_balance=default_conf["dry_run_wallet"],
)
over = hl.hyperopt_loss_function(
@@ -185,7 +217,10 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct
max_date=datetime(2019, 5, 1),
config=default_conf,
processed=None,
backtest_stats={"profit_total": results_over["profit_abs"].sum()},
backtest_stats={
"profit_total": results_over["profit_abs"].sum(),
"results_per_pair": pair_results_over,
},
starting_balance=default_conf["dry_run_wallet"],
)
under = hl.hyperopt_loss_function(
@@ -195,7 +230,10 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct
max_date=datetime(2019, 5, 1),
config=default_conf,
processed=None,
backtest_stats={"profit_total": results_under["profit_abs"].sum()},
backtest_stats={
"profit_total": results_under["profit_abs"].sum(),
"results_per_pair": pair_results_under,
},
starting_balance=default_conf["dry_run_wallet"],
)
assert over < correct

View File

@@ -68,11 +68,21 @@ def test_text_table_bt_results(capsys):
"profit_ratio": [0.1, 0.2, -0.05],
"profit_abs": [0.2, 0.4, -0.1],
"trade_duration": [10, 30, 20],
"close_date": [
dt_utc(2017, 11, 14, 21, 35, 00),
dt_utc(2017, 11, 14, 22, 10, 00),
dt_utc(2017, 11, 14, 22, 43, 00),
],
}
)
pair_results = generate_pair_metrics(
["ETH/BTC"], stake_currency="BTC", starting_balance=4, results=results
["ETH/BTC"],
stake_currency="BTC",
starting_balance=4,
results=results,
min_date=dt_from_ts(1510688220),
max_date=dt_from_ts(1510700340),
)
text_table_bt_results(pair_results, stake_currency="BTC", title="title")
text = capsys.readouterr().out
@@ -420,6 +430,10 @@ def test_generate_pair_metrics():
"profit_ratio": [0.1, 0.2],
"profit_abs": [0.2, 0.4],
"trade_duration": [10, 30],
"close_date": [
dt_utc(2017, 11, 14, 21, 35, 00),
dt_utc(2017, 11, 14, 22, 10, 00),
],
"wins": [2, 0],
"draws": [0, 0],
"losses": [0, 0],
@@ -427,7 +441,12 @@ def test_generate_pair_metrics():
)
pair_results = generate_pair_metrics(
["ETH/BTC"], stake_currency="BTC", starting_balance=2, results=results
["ETH/BTC"],
stake_currency="BTC",
starting_balance=2,
results=results,
min_date=dt_from_ts(1510688220),
max_date=dt_from_ts(1510700340),
)
assert isinstance(pair_results, list)
assert len(pair_results) == 2
@@ -512,6 +531,11 @@ def test_text_table_exit_reason(capsys):
"profit_ratio": [0.1, 0.2, -0.1],
"profit_abs": [0.2, 0.4, -0.2],
"trade_duration": [10, 30, 10],
"close_date": [
dt_utc(2017, 11, 14, 21, 35, 00),
dt_utc(2017, 11, 14, 22, 10, 00),
dt_utc(2017, 11, 14, 22, 43, 00),
],
"wins": [2, 0, 0],
"draws": [0, 0, 0],
"losses": [0, 0, 1],
@@ -520,7 +544,12 @@ def test_text_table_exit_reason(capsys):
)
exit_reason_stats = generate_tag_metrics(
"exit_reason", starting_balance=22, results=results, skip_nan=False
"exit_reason",
starting_balance=22,
results=results,
min_date=dt_from_ts(1510688220),
max_date=dt_from_ts(1510700340),
skip_nan=False,
)
text_table_tags("exit_tag", exit_reason_stats, "BTC")
text = capsys.readouterr().out
@@ -550,6 +579,11 @@ def test_generate_sell_reason_stats():
"profit_ratio": [0.1, 0.2, -0.1],
"profit_abs": [0.2, 0.4, -0.2],
"trade_duration": [10, 30, 10],
"close_date": [
dt_utc(2017, 11, 14, 21, 35, 00),
dt_utc(2017, 11, 14, 22, 10, 00),
dt_utc(2017, 11, 14, 22, 43, 00),
],
"wins": [2, 0, 0],
"draws": [0, 0, 0],
"losses": [0, 0, 1],
@@ -558,7 +592,12 @@ def test_generate_sell_reason_stats():
)
exit_reason_stats = generate_tag_metrics(
"exit_reason", starting_balance=22, results=results, skip_nan=False
"exit_reason",
starting_balance=22,
results=results,
min_date=dt_from_ts(1510688220),
max_date=dt_from_ts(1510700340),
skip_nan=False,
)
roi_result = exit_reason_stats[0]
assert roi_result["key"] == "roi"

View File

@@ -880,68 +880,68 @@ def test_calc_close_trade_price(
"exchange,is_short,lev,close_rate,fee_close,profit,profit_ratio,trading_mode,funding_fees",
[
("binance", False, 1, 2.1, 0.0025, 2.6925, 0.044763092, spot, 0),
("binance", False, 3, 2.1, 0.0025, 2.69166667, 0.134247714, margin, 0),
("binance", False, 3, 2.1, 0.0025, 8.075, 0.134247714, margin, 0),
("binance", True, 1, 2.1, 0.0025, -3.3088157, -0.055285142, margin, 0),
("binance", True, 3, 2.1, 0.0025, -3.3088157, -0.16585542, margin, 0),
("binance", True, 3, 2.1, 0.0025, -9.92644709, -0.16585542, margin, 0),
("binance", False, 1, 1.9, 0.0025, -3.2925, -0.054738154, margin, 0),
("binance", False, 3, 1.9, 0.0025, -3.29333333, -0.164256026, margin, 0),
("binance", False, 3, 1.9, 0.0025, -9.88, -0.164256026, margin, 0),
("binance", True, 1, 1.9, 0.0025, 2.70630953, 0.0452182043, margin, 0),
("binance", True, 3, 1.9, 0.0025, 2.70630953, 0.135654613, margin, 0),
("binance", True, 3, 1.9, 0.0025, 8.11892859, 0.135654613, margin, 0),
("binance", False, 1, 2.2, 0.0025, 5.685, 0.09451371, margin, 0),
("binance", False, 3, 2.2, 0.0025, 5.68416667, 0.28349958, margin, 0),
("binance", False, 3, 2.2, 0.0025, 17.0525, 0.28349958, margin, 0),
("binance", True, 1, 2.2, 0.0025, -6.3163784, -0.10553681, margin, 0),
("binance", True, 3, 2.2, 0.0025, -6.3163784, -0.31661044, margin, 0),
("binance", True, 3, 2.2, 0.0025, -18.94913, -0.31661044, margin, 0),
# Kraken
("kraken", False, 1, 2.1, 0.0025, 2.6925, 0.044763092, spot, 0),
("kraken", False, 3, 2.1, 0.0025, 2.6525, 0.132294264, margin, 0),
("kraken", False, 3, 2.1, 0.0025, 7.9575, 0.132294264, margin, 0),
("kraken", True, 1, 2.1, 0.0025, -3.3706575, -0.056318421, margin, 0),
("kraken", True, 3, 2.1, 0.0025, -3.3706575, -0.168955263, margin, 0),
("kraken", True, 3, 2.1, 0.0025, -10.1119725, -0.168955263, margin, 0),
("kraken", False, 1, 1.9, 0.0025, -3.2925, -0.054738154, margin, 0),
("kraken", False, 3, 1.9, 0.0025, -3.3325, -0.166209476, margin, 0),
("kraken", False, 3, 1.9, 0.0025, -9.9975, -0.166209476, margin, 0),
("kraken", True, 1, 1.9, 0.0025, 2.6503575, 0.044283333, margin, 0),
("kraken", True, 3, 1.9, 0.0025, 2.6503575, 0.132850000, margin, 0),
("kraken", True, 3, 1.9, 0.0025, 7.9510725, 0.132850000, margin, 0),
("kraken", False, 1, 2.2, 0.0025, 5.685, 0.09451371, margin, 0),
("kraken", False, 3, 2.2, 0.0025, 5.645, 0.28154613, margin, 0),
("kraken", False, 3, 2.2, 0.0025, 16.935, 0.28154613, margin, 0),
("kraken", True, 1, 2.2, 0.0025, -6.381165, -0.1066192, margin, 0),
("kraken", True, 3, 2.2, 0.0025, -6.381165, -0.3198578, margin, 0),
("kraken", True, 3, 2.2, 0.0025, -19.143495, -0.3198578, margin, 0),
("binance", False, 1, 2.1, 0.003, 2.66100000, 0.044239401, spot, 0),
("binance", False, 1, 1.9, 0.003, -3.3209999, -0.055211970, spot, 0),
("binance", False, 1, 2.2, 0.003, 5.6520000, 0.093965087, spot, 0),
# FUTURES, funding_fee=1
("binance", False, 1, 2.1, 0.0025, 3.6925, 0.06138819, futures, 1),
("binance", False, 3, 2.1, 0.0025, 3.6925, 0.18416458, futures, 1),
("binance", False, 3, 2.1, 0.0025, 9.0775, 0.15091438, futures, 1),
("binance", True, 1, 2.1, 0.0025, -2.3074999, -0.03855472, futures, 1),
("binance", True, 3, 2.1, 0.0025, -2.3074999, -0.11566416, futures, 1),
("binance", True, 3, 2.1, 0.0025, -8.9225, -0.14908104, futures, 1),
("binance", False, 1, 1.9, 0.0025, -2.2925, -0.03811305, futures, 1),
("binance", False, 3, 1.9, 0.0025, -2.2925, -0.11433915, futures, 1),
("binance", False, 3, 1.9, 0.0025, -8.8775, -0.14758936, futures, 1),
("binance", True, 1, 1.9, 0.0025, 3.7075, 0.06194653, futures, 1),
("binance", True, 3, 1.9, 0.0025, 3.7075, 0.18583959, futures, 1),
("binance", True, 3, 1.9, 0.0025, 9.1225, 0.15242272, futures, 1),
("binance", False, 1, 2.2, 0.0025, 6.685, 0.11113881, futures, 1),
("binance", False, 3, 2.2, 0.0025, 6.685, 0.33341645, futures, 1),
("binance", False, 3, 2.2, 0.0025, 18.055, 0.30016625, futures, 1),
("binance", True, 1, 2.2, 0.0025, -5.315, -0.08880534, futures, 1),
("binance", True, 3, 2.2, 0.0025, -5.315, -0.26641604, futures, 1),
("binance", True, 3, 2.2, 0.0025, -17.945, -0.29983292, futures, 1),
# FUTURES, funding_fee=-1
("binance", False, 1, 2.1, 0.0025, 1.6925, 0.02813798, futures, -1),
("binance", False, 3, 2.1, 0.0025, 1.6925, 0.08441396, futures, -1),
("binance", False, 3, 2.1, 0.0025, 7.0775, 0.11766417, futures, -1),
("binance", True, 1, 2.1, 0.0025, -4.307499, -0.07197159, futures, -1),
("binance", True, 3, 2.1, 0.0025, -4.307499, -0.21591478, futures, -1),
("binance", True, 3, 2.1, 0.0025, -10.92249, -0.18249791, futures, -1),
("binance", False, 1, 1.9, 0.0025, -4.292499, -0.07136325, futures, -1),
("binance", False, 3, 1.9, 0.0025, -4.292499, -0.21408977, futures, -1),
("binance", False, 3, 1.9, 0.0025, -10.87749, -0.18083957, futures, -1),
("binance", True, 1, 1.9, 0.0025, 1.7075, 0.02852965, futures, -1),
("binance", True, 3, 1.9, 0.0025, 1.7075, 0.08558897, futures, -1),
("binance", True, 3, 1.9, 0.0025, 7.1225, 0.11900585, futures, -1),
("binance", False, 1, 2.2, 0.0025, 4.684999, 0.07788861, futures, -1),
("binance", False, 3, 2.2, 0.0025, 4.684999, 0.23366583, futures, -1),
("binance", False, 3, 2.2, 0.0025, 16.055, 0.26691604, futures, -1),
("binance", True, 1, 2.2, 0.0025, -7.315, -0.12222222, futures, -1),
("binance", True, 3, 2.2, 0.0025, -7.315, -0.36666666, futures, -1),
("binance", True, 3, 2.2, 0.0025, -19.945, -0.33324979, futures, -1),
# FUTURES, funding_fee=0
("binance", False, 1, 2.1, 0.0025, 2.6925, 0.04476309, futures, 0),
("binance", False, 3, 2.1, 0.0025, 2.6925, 0.13428928, futures, 0),
("binance", False, 3, 2.1, 0.0025, 8.0775, 0.13428928, futures, 0),
("binance", True, 1, 2.1, 0.0025, -3.3074999, -0.05526316, futures, 0),
("binance", True, 3, 2.1, 0.0025, -3.3074999, -0.16578947, futures, 0),
("binance", True, 3, 2.1, 0.0025, -9.9224997, -0.16578947, futures, 0),
("binance", False, 1, 1.9, 0.0025, -3.2925, -0.05473815, futures, 0),
("binance", False, 3, 1.9, 0.0025, -3.2925, -0.16421446, futures, 0),
("binance", False, 3, 1.9, 0.0025, -9.8775, -0.16421446, futures, 0),
("binance", True, 1, 1.9, 0.0025, 2.7075, 0.0452381, futures, 0),
("binance", True, 3, 1.9, 0.0025, 2.7075, 0.13571429, futures, 0),
("binance", True, 3, 1.9, 0.0025, 8.1225, 0.13571429, futures, 0),
],
)
@pytest.mark.usefixtures("init_persistence")
@@ -1162,7 +1162,7 @@ def test_calc_profit(
trade = Trade(
pair="ADA/USDT",
stake_amount=60.0,
amount=30.0,
amount=30.0 * lev,
open_rate=2.0,
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
interest_rate=0.0005,
@@ -1183,7 +1183,7 @@ def test_calc_profit(
assert pytest.approx(val) == profit_res.profit_abs
assert pytest.approx(profit_res.total_profit) == round(profit, 8)
# assert pytest.approx(profit_res.total_profit_ratio) == round(profit_ratio, 8)
assert pytest.approx(profit_res.total_profit_ratio) == round(profit_ratio, 8)
assert pytest.approx(trade.calc_profit(rate=close_rate)) == round(profit, 8)
assert pytest.approx(trade.calc_profit_ratio(rate=close_rate)) == round(profit_ratio, 8)
@@ -1193,7 +1193,7 @@ def test_calc_profit(
assert pytest.approx(profit_res2.profit_ratio) == round(profit_ratio, 8)
assert pytest.approx(profit_res2.total_profit) == round(profit, 8)
# assert pytest.approx(profit_res2.total_profit_ratio) == round(profit_ratio, 8)
assert pytest.approx(profit_res2.total_profit_ratio) == round(profit_ratio, 8)
assert pytest.approx(trade.calc_profit(close_rate, trade.amount, trade.open_rate)) == round(
profit, 8

View File

@@ -281,7 +281,7 @@ def test_remove_logs_for_pairs_already_in_blacklist(mocker, markets, static_pl_c
for _ in range(3):
new_whitelist = freqtrade.pairlists.verify_blacklist(
whitelist + ["BLK/BTC"], logger.warning
[*whitelist, "BLK/BTC"], logger.warning
)
# Ensure that the pair is removed from the white list, and properly logged.
assert set(whitelist) == set(new_whitelist)
@@ -2032,11 +2032,7 @@ def test_expand_pairlist(wildcardlist, pairs, expected):
},
}
assert sorted(dynamic_expand_pairlist(conf, pairs)) == sorted(
expected
+ [
"BTC/USDT:USDT",
"XRP/BUSD",
]
[*expected, "BTC/USDT:USDT", "XRP/BUSD"]
)
@@ -2138,7 +2134,7 @@ def test_ProducerPairlist(mocker, whitelist_conf, markets):
dp = DataProvider(whitelist_conf, exchange, None)
pairs = ["ETH/BTC", "LTC/BTC", "XRP/BTC"]
# different producer
dp._set_producer_pairs(pairs + ["MEEP/USDT"], "default")
dp._set_producer_pairs([*pairs, "MEEP/USDT"], "default")
pm = PairListManager(exchange, whitelist_conf, dp)
pm.refresh_pairlist()
assert pm.whitelist == []
@@ -2161,7 +2157,7 @@ def test_ProducerPairlist(mocker, whitelist_conf, markets):
pm = PairListManager(exchange, whitelist_conf, dp)
pm.refresh_pairlist()
assert len(pm.whitelist) == 4
assert pm.whitelist == ["TKN/BTC"] + pairs
assert pm.whitelist == ["TKN/BTC", *pairs]
@pytest.mark.usefixtures("init_persistence")

View File

@@ -19,8 +19,8 @@ def generate_mock_trade(
fee: float,
is_open: bool,
exit_reason: str = ExitType.EXIT_SIGNAL,
min_ago_open: int = None,
min_ago_close: int = None,
min_ago_open: int | None = None,
min_ago_close: int | None = None,
profit_rate: float = 0.9,
is_short: bool = False,
):

View File

@@ -197,7 +197,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
response.update(
{
"max_stake_amount": 0.001,
"total_profit_ratio": pytest.approx(-0.00409153),
"total_profit_ratio": pytest.approx(-0.00408133),
"has_open_orders": False,
}
)
@@ -228,7 +228,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
assert results[0] == response_norate
def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
def test_rpc_status_table(default_conf, ticker, fee, mocker, time_machine) -> None:
time_machine.move_to("2024-05-10 11:15:00 +00:00", tick=False)
mocker.patch.multiple(
"freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}),
@@ -806,19 +807,19 @@ def test_rpc_stop(mocker, default_conf) -> None:
assert freqtradebot.state == State.STOPPED
def test_rpc_stopentry(mocker, default_conf) -> None:
def test_rpc_pause(mocker, default_conf) -> None:
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
mocker.patch.multiple(EXMS, fetch_ticker=MagicMock())
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING
freqtradebot.state = State.PAUSED
assert freqtradebot.config["max_open_trades"] != 0
result = rpc._rpc_stopentry()
assert {"status": "No more entries will occur from now. Run /reload_config to reset."} == result
assert freqtradebot.config["max_open_trades"] == 0
result = rpc._rpc_pause()
assert {
"status": "paused, no more entries will occur from now. Run /start to enable entries."
} == result
def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:

View File

@@ -533,23 +533,26 @@ def test_api_reloadconf(botclient):
assert ftbot.state == State.RELOAD_CONFIG
def test_api_stopentry(botclient):
def test_api_pause(botclient):
ftbot, client = botclient
assert ftbot.config["max_open_trades"] != 0
rc = client_post(client, f"{BASE_URI}/stopbuy")
rc = client_post(client, f"{BASE_URI}/pause")
assert_response(rc)
assert rc.json() == {
"status": "No more entries will occur from now. Run /reload_config to reset."
"status": "paused, no more entries will occur from now. Run /start to enable entries."
}
rc = client_post(client, f"{BASE_URI}/pause")
assert_response(rc)
assert rc.json() == {
"status": "paused, no more entries will occur from now. Run /start to enable entries."
}
assert ftbot.config["max_open_trades"] == 0
rc = client_post(client, f"{BASE_URI}/stopentry")
assert_response(rc)
assert rc.json() == {
"status": "No more entries will occur from now. Run /reload_config to reset."
"status": "paused, no more entries will occur from now. Run /start to enable entries."
}
assert ftbot.config["max_open_trades"] == 0
def test_api_balance(botclient, mocker, rpc_balance, tickers):
@@ -773,12 +776,28 @@ def test_api_trades(botclient, mocker, fee, markets, is_short):
assert rc.json()["trades_count"] == 2
assert rc.json()["total_trades"] == 2
assert rc.json()["trades"][0]["is_short"] == is_short
# Ensure the trades are sorted by trade_id (the default, see below)
assert rc.json()["trades"][0]["trade_id"] == 2
assert rc.json()["trades"][1]["trade_id"] == 3
rc = client_get(client, f"{BASE_URI}/trades?limit=1")
assert_response(rc)
assert len(rc.json()["trades"]) == 1
assert rc.json()["trades_count"] == 1
assert rc.json()["total_trades"] == 2
# Test ascending order (default)
rc = client_get(client, f"{BASE_URI}/trades?order_by_id=true")
assert_response(rc)
assert rc.json()["trades"][0]["trade_id"] == 2
assert rc.json()["trades"][1]["trade_id"] == 3
# Test descending order
rc = client_get(client, f"{BASE_URI}/trades?order_by_id=false")
assert_response(rc)
assert rc.json()["trades"][0]["trade_id"] == 3
assert rc.json()["trades"][1]["trade_id"] == 2
@pytest.mark.parametrize("is_short", [True, False])
def test_api_trade_single(botclient, mocker, fee, ticker, markets, is_short):
@@ -1845,7 +1864,21 @@ def test_api_pair_candles(botclient, ohlcv_history):
ohlcv_history["exit_short"] = 0
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT)
fake_plot_annotations = [
{
"type": "area",
"start": "2024-01-01 15:00:00",
"end": "2024-01-01 16:00:00",
"y_start": 94000.2,
"y_end": 98000,
"color": "",
"label": "some label",
}
]
plot_annotations_mock = MagicMock(return_value=fake_plot_annotations)
ftbot.strategy.plot_annotations = plot_annotations_mock
for call in ("get", "post"):
plot_annotations_mock.reset_mock()
if call == "get":
rc = client_get(
client,
@@ -1875,6 +1908,8 @@ def test_api_pair_candles(botclient, ohlcv_history):
assert resp["data_start_ts"] == 1511686200000
assert resp["data_stop"] == "2017-11-26 09:00:00+00:00"
assert resp["data_stop_ts"] == 1511686800000
assert resp["annotations"] == fake_plot_annotations
assert plot_annotations_mock.call_count == 1
assert isinstance(resp["columns"], list)
base_cols = {
"date",
@@ -2203,8 +2238,8 @@ def test_api_pair_history(botclient, tmp_path, mocker):
assert len(result["columns"]) == col_count
assert len(result["all_columns"]) == 25
assert len(data[0]) == col_count
date_col_idx = [idx for idx, c in enumerate(result["columns"]) if c == "date"][0]
rsi_col_idx = [idx for idx, c in enumerate(result["columns"]) if c == "rsi"][0]
date_col_idx = next(idx for idx, c in enumerate(result["columns"]) if c == "date")
rsi_col_idx = next(idx for idx, c in enumerate(result["columns"]) if c == "rsi")
assert data[0][date_col_idx] == "2018-01-11T00:00:00Z"
assert data[0][rsi_col_idx] is not None
@@ -2216,6 +2251,7 @@ def test_api_pair_history(botclient, tmp_path, mocker):
assert result["data_start_ts"] == 1515628800000
assert result["data_stop"] == "2018-01-12 00:00:00+00:00"
assert result["data_stop_ts"] == 1515715200000
assert result["annotations"] == []
lfm.reset_mock()
# No data found
@@ -2429,7 +2465,7 @@ def test_api_exchanges(botclient):
response = rc.json()
assert isinstance(response["exchanges"], list)
assert len(response["exchanges"]) > 20
okx = [x for x in response["exchanges"] if x["classname"] == "okx"][0]
okx = next(x for x in response["exchanges"] if x["classname"] == "okx")
assert okx == {
"classname": "okx",
"name": "OKX",
@@ -2445,7 +2481,7 @@ def test_api_exchanges(botclient):
],
}
mexc = [x for x in response["exchanges"] if x["classname"] == "mexc"][0]
mexc = next(x for x in response["exchanges"] if x["classname"] == "mexc")
assert mexc == {
"classname": "mexc",
"name": "MEXC Global",
@@ -2457,7 +2493,7 @@ def test_api_exchanges(botclient):
"alias_for": None,
"trade_modes": [{"trading_mode": "spot", "margin_mode": ""}],
}
waves = [x for x in response["exchanges"] if x["classname"] == "wavesexchange"][0]
waves = next(x for x in response["exchanges"] if x["classname"] == "wavesexchange")
assert waves == {
"classname": "wavesexchange",
"name": "Waves.Exchange",
@@ -2551,10 +2587,10 @@ def test_api_pairlists_available(botclient, tmp_path):
assert len([r for r in response["pairlists"] if r["name"] == "VolumePairList"]) == 1
assert len([r for r in response["pairlists"] if r["name"] == "StaticPairList"]) == 1
volumepl = [r for r in response["pairlists"] if r["name"] == "VolumePairList"][0]
volumepl = next(r for r in response["pairlists"] if r["name"] == "VolumePairList")
assert volumepl["is_pairlist_generator"] is True
assert len(volumepl["params"]) > 1
age_pl = [r for r in response["pairlists"] if r["name"] == "AgeFilter"][0]
age_pl = next(r for r in response["pairlists"] if r["name"] == "AgeFilter")
assert age_pl["is_pairlist_generator"] is False
assert len(volumepl["params"]) > 2
@@ -2850,7 +2886,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmp_path):
def test_api_backtest_history(botclient, mocker, testdatadir):
ftbot, client = botclient
mocker.patch(
"freqtrade.data.btanalysis._get_backtest_files",
"freqtrade.data.btanalysis.bt_fileutils._get_backtest_files",
return_value=[
testdatadir / "backtest_results/backtest-result_multistrat.json",
testdatadir / "backtest_results/backtest-result.json",

View File

@@ -169,7 +169,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
"['stats'], ['daily'], ['weekly'], ['monthly'], "
"['count'], ['locks'], ['delete_locks', 'unlock'], "
"['reload_conf', 'reload_config'], ['show_conf', 'show_config'], "
"['stopbuy', 'stopentry'], ['whitelist'], ['blacklist'], "
"['pause', 'stopbuy', 'stopentry'], ['whitelist'], ['blacklist'], "
"['bl_delete', 'blacklist_delete'], "
"['logs'], ['edge'], ['health'], ['help'], ['version'], ['marketdir'], "
"['order'], ['list_custom_data'], ['tg_info']]"
@@ -1222,15 +1222,15 @@ async def test_stop_handle_already_stopped(default_conf, update, mocker) -> None
assert "already stopped" in msg_mock.call_args_list[0][0][0]
async def test_stopbuy_handle(default_conf, update, mocker) -> None:
async def test_pause_handle(default_conf, update, mocker) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
assert freqtradebot.config["max_open_trades"] != 0
await telegram._stopentry(update=update, context=MagicMock())
assert freqtradebot.config["max_open_trades"] == 0
assert freqtradebot.state == State.RUNNING
await telegram._pause(update=update, context=MagicMock())
assert freqtradebot.state == State.PAUSED
assert msg_mock.call_count == 1
assert (
"No more entries will occur from now. Run /reload_config to reset."
"paused, no more entries will occur from now. Run /start to enable entries."
in msg_mock.call_args_list[0][0][0]
)

View File

@@ -5,11 +5,54 @@ from unittest.mock import MagicMock
import pytest
from freqtrade.commands import Arguments
from freqtrade.commands.cli_options import check_int_nonzero, check_int_positive
from freqtrade.commands import Arguments, arguments
from freqtrade.commands.cli_options import (
AVAILABLE_CLI_OPTIONS,
check_int_nonzero,
check_int_positive,
)
from tests.conftest import CURRENT_TEST_STRATEGY
def test_available_cli_options():
"""
AVAILABLE_CLI_OPTIONS has keys that are the union of the values in all ARGS_* - required by CLI
each of the ARGS_* lists has a list of members which is assumed to also
be in AVAILABLE_CLI_OPTIONS
"""
args_union = {
arg
for variable, value in vars(arguments).items()
if variable.startswith("ARGS_")
for arg in value
}
expected_options = set(AVAILABLE_CLI_OPTIONS)
only_in_command_args = expected_options.difference(args_union)
only_in_all_args = args_union.difference(expected_options)
if only_in_all_args or only_in_command_args:
pytest.fail(
"variables around command line arguments not kept in sync:\n"
f" * args only in some ARGS_* list but not AVAILABLE_CLI_OPTIONS: {only_in_all_args}\n"
" * args only in AVAILABLE_CLI_OPTIONS but not some ARGS_* list: "
f"{only_in_command_args}"
)
def test_arguments_match_available_cli_options(monkeypatch):
"""All entries in AVAILABLE_CLI_OPTIONS are used in argument parsing."""
parsed_options = set()
actual_build_args = Arguments._build_args
def build_args_monitor(self, optionlist, parser):
parsed_options.update(optionlist)
return actual_build_args(self, optionlist=optionlist, parser=parser)
monkeypatch.setattr(Arguments, "_build_args", build_args_monitor)
# this will result in a parser being built so we can check the arguments used
Arguments([]).get_parsed_arg()
assert parsed_options == set(AVAILABLE_CLI_OPTIONS)
# Parse common command-line-arguments. Used for all tools
def test_parse_args_none() -> None:
arguments = Arguments(["trade"])