2017-11-18 07:45:01 +00:00
|
|
|
# pragma pylint: disable=missing-docstring,C0103
|
2017-12-11 07:56:03 +00:00
|
|
|
|
2022-03-18 05:58:22 +00:00
|
|
|
from copy import deepcopy
|
2019-08-16 11:04:07 +00:00
|
|
|
from pathlib import Path
|
2018-02-04 07:33:54 +00:00
|
|
|
from unittest.mock import MagicMock
|
2018-03-17 21:44:47 +00:00
|
|
|
|
2023-01-11 21:07:20 +00:00
|
|
|
import pandas as pd
|
2020-01-05 09:36:08 +00:00
|
|
|
import pytest
|
|
|
|
|
2022-09-09 23:14:40 +00:00
|
|
|
from freqtrade.misc import (dataframe_to_json, decimals_per_coin, deep_merge_dicts, file_dump_json,
|
2023-07-25 18:19:23 +00:00
|
|
|
file_load_json, is_file_in_dir, json_to_dataframe, pair_to_filename,
|
2023-08-15 05:42:43 +00:00
|
|
|
parse_db_uri_for_logging, plural, round_coin_value, safe_value_fallback,
|
2023-05-15 17:55:54 +00:00
|
|
|
safe_value_fallback2)
|
2021-02-12 19:32:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_decimals_per_coin():
|
|
|
|
assert decimals_per_coin('USDT') == 3
|
|
|
|
assert decimals_per_coin('EUR') == 3
|
|
|
|
assert decimals_per_coin('BTC') == 8
|
|
|
|
assert decimals_per_coin('ETH') == 5
|
|
|
|
|
|
|
|
|
|
|
|
def test_round_coin_value():
|
|
|
|
assert round_coin_value(222.222222, 'USDT') == '222.222 USDT'
|
2022-02-19 15:35:17 +00:00
|
|
|
assert round_coin_value(222.2, 'USDT', keep_trailing_zeros=True) == '222.200 USDT'
|
|
|
|
assert round_coin_value(222.2, 'USDT') == '222.2 USDT'
|
2021-02-12 19:32:41 +00:00
|
|
|
assert round_coin_value(222.12745, 'EUR') == '222.127 EUR'
|
|
|
|
assert round_coin_value(0.1274512123, 'BTC') == '0.12745121 BTC'
|
|
|
|
assert round_coin_value(0.1274512123, 'ETH') == '0.12745 ETH'
|
|
|
|
|
|
|
|
assert round_coin_value(222.222222, 'USDT', False) == '222.222'
|
2022-02-19 15:35:17 +00:00
|
|
|
assert round_coin_value(222.2, 'USDT', False) == '222.2'
|
|
|
|
assert round_coin_value(222.00, 'USDT', False) == '222'
|
2021-02-12 19:32:41 +00:00
|
|
|
assert round_coin_value(222.12745, 'EUR', False) == '222.127'
|
|
|
|
assert round_coin_value(0.1274512123, 'BTC', False) == '0.12745121'
|
|
|
|
assert round_coin_value(0.1274512123, 'ETH', False) == '0.12745'
|
2022-02-19 15:35:17 +00:00
|
|
|
assert round_coin_value(222.2, 'USDT', False, True) == '222.200'
|
2017-11-20 18:37:25 +00:00
|
|
|
|
|
|
|
|
2018-02-04 09:21:16 +00:00
|
|
|
def test_file_dump_json(mocker) -> None:
|
2023-02-25 16:08:02 +00:00
|
|
|
file_open = mocker.patch('freqtrade.misc.Path.open', MagicMock())
|
2018-12-28 09:01:16 +00:00
|
|
|
json_dump = mocker.patch('rapidjson.dump', MagicMock())
|
2019-08-16 11:04:07 +00:00
|
|
|
file_dump_json(Path('somefile'), [1, 2, 3])
|
2018-02-04 07:33:54 +00:00
|
|
|
assert file_open.call_count == 1
|
|
|
|
assert json_dump.call_count == 1
|
2018-03-30 21:30:23 +00:00
|
|
|
file_open = mocker.patch('freqtrade.misc.gzip.open', MagicMock())
|
2018-12-28 09:01:16 +00:00
|
|
|
json_dump = mocker.patch('rapidjson.dump', MagicMock())
|
2019-08-16 11:04:07 +00:00
|
|
|
file_dump_json(Path('somefile'), [1, 2, 3], True)
|
2018-03-30 21:30:23 +00:00
|
|
|
assert file_open.call_count == 1
|
|
|
|
assert json_dump.call_count == 1
|
2018-04-10 18:09:14 +00:00
|
|
|
|
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
def test_file_load_json(mocker, testdatadir) -> None:
|
2018-12-28 09:25:12 +00:00
|
|
|
|
|
|
|
# 7m .json does not exist
|
2019-12-27 09:49:01 +00:00
|
|
|
ret = file_load_json(testdatadir / 'UNITTEST_BTC-7m.json')
|
2018-12-28 09:25:12 +00:00
|
|
|
assert not ret
|
|
|
|
# 1m json exists (but no .gz exists)
|
2019-12-27 09:49:01 +00:00
|
|
|
ret = file_load_json(testdatadir / 'UNITTEST_BTC-1m.json')
|
2018-12-28 09:25:12 +00:00
|
|
|
assert ret
|
|
|
|
# 8 .json is empty and will fail if it's loaded. .json.gz is a copy of 1.json
|
2019-12-27 09:49:01 +00:00
|
|
|
ret = file_load_json(testdatadir / 'UNITTEST_BTC-8m.json')
|
2018-12-28 09:25:12 +00:00
|
|
|
assert ret
|
|
|
|
|
|
|
|
|
2023-07-25 18:19:23 +00:00
|
|
|
def test_is_file_in_dir(tmp_path):
|
|
|
|
|
|
|
|
# Create a temporary directory and file
|
|
|
|
dir_path = tmp_path / "subdir"
|
|
|
|
dir_path.mkdir()
|
|
|
|
file_path = dir_path / "test.txt"
|
|
|
|
file_path.touch()
|
|
|
|
|
|
|
|
# Test that the function returns True when the file is in the directory
|
|
|
|
assert is_file_in_dir(file_path, dir_path) is True
|
|
|
|
|
|
|
|
# Test that the function returns False when the file is not in the directory
|
|
|
|
assert is_file_in_dir(file_path, tmp_path) is False
|
|
|
|
|
|
|
|
file_path2 = tmp_path / "../../test2.txt"
|
|
|
|
assert is_file_in_dir(file_path2, tmp_path) is False
|
|
|
|
|
|
|
|
|
2020-01-05 09:36:08 +00:00
|
|
|
@pytest.mark.parametrize("pair,expected_result", [
|
|
|
|
("ETH/BTC", 'ETH_BTC'),
|
2021-11-23 19:02:07 +00:00
|
|
|
("ETH/USDT", 'ETH_USDT'),
|
|
|
|
("ETH/USDT:USDT", 'ETH_USDT_USDT'), # swap with USDT as settlement currency
|
2022-03-27 14:38:12 +00:00
|
|
|
("ETH/USD:USD", 'ETH_USD_USD'), # swap with USD as settlement currency
|
|
|
|
("AAVE/USD:USD", 'AAVE_USD_USD'), # swap with USDT as settlement currency
|
2021-12-02 18:05:06 +00:00
|
|
|
("ETH/USDT:USDT-210625", 'ETH_USDT_USDT-210625'), # expiring futures
|
2020-01-05 09:36:08 +00:00
|
|
|
("Fabric Token/ETH", 'Fabric_Token_ETH'),
|
|
|
|
("ETHH20", 'ETHH20'),
|
|
|
|
(".XBTBON2H", '_XBTBON2H'),
|
|
|
|
("ETHUSD.d", 'ETHUSD_d'),
|
2021-11-28 14:08:02 +00:00
|
|
|
("ADA-0327", 'ADA-0327'),
|
|
|
|
("BTC-USD-200110", 'BTC-USD-200110'),
|
|
|
|
("BTC-PERP:USDT", 'BTC-PERP_USDT'),
|
|
|
|
("F-AKRO/USDT", 'F-AKRO_USDT'),
|
2020-01-05 09:36:08 +00:00
|
|
|
("LC+/ETH", 'LC__ETH'),
|
|
|
|
("CMT@18/ETH", 'CMT_18_ETH'),
|
|
|
|
("LBTC:1022/SAI", 'LBTC_1022_SAI'),
|
|
|
|
("$PAC/BTC", '_PAC_BTC'),
|
|
|
|
("ACC_OLD/BTC", 'ACC_OLD_BTC'),
|
|
|
|
])
|
|
|
|
def test_pair_to_filename(pair, expected_result):
|
|
|
|
pair_s = pair_to_filename(pair)
|
|
|
|
assert pair_s == expected_result
|
|
|
|
|
|
|
|
|
2020-04-09 17:34:48 +00:00
|
|
|
def test_safe_value_fallback():
|
2020-07-15 17:49:51 +00:00
|
|
|
dict1 = {'keya': None, 'keyb': 2, 'keyc': 5, 'keyd': None}
|
|
|
|
assert safe_value_fallback(dict1, 'keya', 'keyb') == 2
|
|
|
|
assert safe_value_fallback(dict1, 'keyb', 'keya') == 2
|
|
|
|
|
|
|
|
assert safe_value_fallback(dict1, 'keyb', 'keyc') == 2
|
|
|
|
assert safe_value_fallback(dict1, 'keya', 'keyc') == 5
|
|
|
|
|
|
|
|
assert safe_value_fallback(dict1, 'keyc', 'keyb') == 5
|
|
|
|
|
|
|
|
assert safe_value_fallback(dict1, 'keya', 'keyd') is None
|
|
|
|
|
|
|
|
assert safe_value_fallback(dict1, 'keyNo', 'keyNo') is None
|
|
|
|
assert safe_value_fallback(dict1, 'keyNo', 'keyNo', 55) == 55
|
2023-09-11 18:03:08 +00:00
|
|
|
assert safe_value_fallback(dict1, 'keyNo', default_value=55) == 55
|
|
|
|
assert safe_value_fallback(dict1, 'keyNo', None, default_value=55) == 55
|
2020-07-15 17:49:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_safe_value_fallback2():
|
2020-04-09 17:40:04 +00:00
|
|
|
dict1 = {'keya': None, 'keyb': 2, 'keyc': 5, 'keyd': None}
|
|
|
|
dict2 = {'keya': 20, 'keyb': None, 'keyc': 6, 'keyd': None}
|
2020-07-15 17:49:51 +00:00
|
|
|
assert safe_value_fallback2(dict1, dict2, 'keya', 'keya') == 20
|
|
|
|
assert safe_value_fallback2(dict2, dict1, 'keya', 'keya') == 20
|
2020-04-09 17:34:48 +00:00
|
|
|
|
2020-07-15 17:49:51 +00:00
|
|
|
assert safe_value_fallback2(dict1, dict2, 'keyb', 'keyb') == 2
|
|
|
|
assert safe_value_fallback2(dict2, dict1, 'keyb', 'keyb') == 2
|
2020-04-09 17:34:48 +00:00
|
|
|
|
2020-07-15 17:49:51 +00:00
|
|
|
assert safe_value_fallback2(dict1, dict2, 'keyc', 'keyc') == 5
|
|
|
|
assert safe_value_fallback2(dict2, dict1, 'keyc', 'keyc') == 6
|
2020-04-09 17:34:48 +00:00
|
|
|
|
2020-07-15 17:49:51 +00:00
|
|
|
assert safe_value_fallback2(dict1, dict2, 'keyd', 'keyd') is None
|
|
|
|
assert safe_value_fallback2(dict2, dict1, 'keyd', 'keyd') is None
|
|
|
|
assert safe_value_fallback2(dict2, dict1, 'keyd', 'keyd', 1234) == 1234
|
2020-04-09 17:40:04 +00:00
|
|
|
|
2020-07-15 17:49:51 +00:00
|
|
|
assert safe_value_fallback2(dict1, dict2, 'keyNo', 'keyNo') is None
|
|
|
|
assert safe_value_fallback2(dict2, dict1, 'keyNo', 'keyNo') is None
|
|
|
|
assert safe_value_fallback2(dict2, dict1, 'keyNo', 'keyNo', 1234) == 1234
|
2020-04-09 17:40:04 +00:00
|
|
|
|
2020-04-09 17:34:48 +00:00
|
|
|
|
2019-10-17 14:52:33 +00:00
|
|
|
def test_plural() -> None:
|
|
|
|
assert plural(0, "page") == "pages"
|
|
|
|
assert plural(0.0, "page") == "pages"
|
|
|
|
assert plural(1, "page") == "page"
|
|
|
|
assert plural(1.0, "page") == "page"
|
|
|
|
assert plural(2, "page") == "pages"
|
|
|
|
assert plural(2.0, "page") == "pages"
|
|
|
|
assert plural(-1, "page") == "page"
|
|
|
|
assert plural(-1.0, "page") == "page"
|
|
|
|
assert plural(-2, "page") == "pages"
|
|
|
|
assert plural(-2.0, "page") == "pages"
|
|
|
|
assert plural(0.5, "page") == "pages"
|
|
|
|
assert plural(1.5, "page") == "pages"
|
|
|
|
assert plural(-0.5, "page") == "pages"
|
|
|
|
assert plural(-1.5, "page") == "pages"
|
|
|
|
|
|
|
|
assert plural(0, "ox", "oxen") == "oxen"
|
|
|
|
assert plural(0.0, "ox", "oxen") == "oxen"
|
|
|
|
assert plural(1, "ox", "oxen") == "ox"
|
|
|
|
assert plural(1.0, "ox", "oxen") == "ox"
|
|
|
|
assert plural(2, "ox", "oxen") == "oxen"
|
|
|
|
assert plural(2.0, "ox", "oxen") == "oxen"
|
|
|
|
assert plural(-1, "ox", "oxen") == "ox"
|
|
|
|
assert plural(-1.0, "ox", "oxen") == "ox"
|
|
|
|
assert plural(-2, "ox", "oxen") == "oxen"
|
|
|
|
assert plural(-2.0, "ox", "oxen") == "oxen"
|
|
|
|
assert plural(0.5, "ox", "oxen") == "oxen"
|
|
|
|
assert plural(1.5, "ox", "oxen") == "oxen"
|
|
|
|
assert plural(-0.5, "ox", "oxen") == "oxen"
|
|
|
|
assert plural(-1.5, "ox", "oxen") == "oxen"
|
2020-03-01 08:34:42 +00:00
|
|
|
|
|
|
|
|
2021-12-15 18:25:30 +00:00
|
|
|
@pytest.mark.parametrize('conn_url,expected', [
|
|
|
|
("postgresql+psycopg2://scott123:scott123@host:1245/dbname",
|
|
|
|
"postgresql+psycopg2://scott123:*****@host:1245/dbname"),
|
|
|
|
("postgresql+psycopg2://scott123:scott123@host.name.com/dbname",
|
|
|
|
"postgresql+psycopg2://scott123:*****@host.name.com/dbname"),
|
|
|
|
("mariadb+mariadbconnector://app_user:Password123!@127.0.0.1:3306/company",
|
|
|
|
"mariadb+mariadbconnector://app_user:*****@127.0.0.1:3306/company"),
|
|
|
|
("mysql+pymysql://user:pass@some_mariadb/dbname?charset=utf8mb4",
|
|
|
|
"mysql+pymysql://user:*****@some_mariadb/dbname?charset=utf8mb4"),
|
|
|
|
("sqlite:////freqtrade/user_data/tradesv3.sqlite",
|
|
|
|
"sqlite:////freqtrade/user_data/tradesv3.sqlite"),
|
|
|
|
])
|
|
|
|
def test_parse_db_uri_for_logging(conn_url, expected) -> None:
|
|
|
|
|
|
|
|
assert parse_db_uri_for_logging(conn_url) == expected
|
2022-03-18 05:58:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_deep_merge_dicts():
|
|
|
|
a = {'first': {'rows': {'pass': 'dog', 'number': '1', 'test': None}}}
|
|
|
|
b = {'first': {'rows': {'fail': 'cat', 'number': '5', 'test': 'asdf'}}}
|
|
|
|
res = {'first': {'rows': {'pass': 'dog', 'fail': 'cat', 'number': '5', 'test': 'asdf'}}}
|
|
|
|
res2 = {'first': {'rows': {'pass': 'dog', 'fail': 'cat', 'number': '1', 'test': None}}}
|
|
|
|
assert deep_merge_dicts(b, deepcopy(a)) == res
|
|
|
|
|
|
|
|
assert deep_merge_dicts(a, deepcopy(b)) == res2
|
|
|
|
|
|
|
|
res2['first']['rows']['test'] = 'asdf'
|
|
|
|
assert deep_merge_dicts(a, deepcopy(b), allow_null_overrides=False) == res2
|
2022-09-09 23:14:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_dataframe_json(ohlcv_history):
|
|
|
|
from pandas.testing import assert_frame_equal
|
|
|
|
json = dataframe_to_json(ohlcv_history)
|
|
|
|
dataframe = json_to_dataframe(json)
|
|
|
|
|
|
|
|
assert list(ohlcv_history.columns) == list(dataframe.columns)
|
|
|
|
assert len(ohlcv_history) == len(dataframe)
|
|
|
|
|
|
|
|
assert_frame_equal(ohlcv_history, dataframe)
|
2023-01-11 21:07:20 +00:00
|
|
|
ohlcv_history.at[1, 'date'] = pd.NaT
|
|
|
|
json = dataframe_to_json(ohlcv_history)
|
|
|
|
|
|
|
|
dataframe = json_to_dataframe(json)
|