Merge branch 'develop' into feat/pairlistconfig

This commit is contained in:
Matthias
2023-05-21 09:15:50 +02:00
108 changed files with 3293 additions and 942 deletions
+9 -2
View File
@@ -181,7 +181,7 @@ def get_patched_exchange(mocker, config, api_mock=None, id='binance',
patch_exchange(mocker, api_mock, id, mock_markets, mock_supported_modes)
config['exchange']['name'] = id
try:
exchange = ExchangeResolver.load_exchange(id, config, load_leverage_tiers=True)
exchange = ExchangeResolver.load_exchange(config, load_leverage_tiers=True)
except ImportError:
exchange = Exchange(config)
return exchange
@@ -411,6 +411,14 @@ def patch_gc(mocker) -> None:
mocker.patch("freqtrade.main.gc_set_threshold")
@pytest.fixture(autouse=True)
def user_dir(mocker, tmpdir) -> Path:
user_dir = Path(tmpdir) / "user_data"
mocker.patch('freqtrade.configuration.configuration.create_userdata_dir',
return_value=user_dir)
return user_dir
@pytest.fixture(autouse=True)
def patch_coingekko(mocker) -> None:
"""
@@ -485,7 +493,6 @@ def get_default_conf(testdatadir):
},
"exchange": {
"name": "binance",
"enabled": True,
"key": "key",
"secret": "secret",
"pair_whitelist": [
+14 -4
View File
@@ -18,8 +18,9 @@ def entryexitanalysis_cleanup() -> None:
Backtesting.cleanup()
def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, tmpdir, capsys):
def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, user_dir, capsys):
caplog.set_level(logging.INFO)
(user_dir / 'backtest_results').mkdir(parents=True, exist_ok=True)
default_conf.update({
"use_exit_signal": True,
@@ -80,7 +81,7 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, tmp
'backtesting',
'--config', 'config.json',
'--datadir', str(testdatadir),
'--user-data-dir', str(tmpdir),
'--user-data-dir', str(user_dir),
'--timeframe', '5m',
'--timerange', '1515560100-1517287800',
'--export', 'signals',
@@ -98,7 +99,7 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, tmp
'backtesting-analysis',
'--config', 'config.json',
'--datadir', str(testdatadir),
'--user-data-dir', str(tmpdir),
'--user-data-dir', str(user_dir),
]
# test group 0 and indicator list
@@ -200,8 +201,17 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, tmp
assert 'trailing_stop_loss' in captured.out
# test date filtering
args = get_args(base_args + ['--timerange', "20180129-20180130"])
args = get_args(base_args +
['--analysis-groups', "0", "1", "2",
'--timerange', "20180129-20180130"]
)
start_analysis_entries_exits(args)
captured = capsys.readouterr()
assert 'enter_tag_long_a' in captured.out
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'])
start_analysis_entries_exits(args)
captured = capsys.readouterr()
assert 'no rejected signals' in captured.out
+2 -2
View File
@@ -302,7 +302,7 @@ def exchange(request, exchange_conf):
exchange_conf, EXCHANGES[request.param].get('use_ci_proxy', False))
exchange_conf['exchange']['name'] = request.param
exchange_conf['stake_currency'] = EXCHANGES[request.param]['stake_currency']
exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True)
exchange = ExchangeResolver.load_exchange(exchange_conf, validate=True)
yield exchange, request.param
@@ -330,7 +330,7 @@ def exchange_futures(request, exchange_conf, class_mocker):
class_mocker.patch(f'{EXMS}.cache_leverage_tiers')
exchange = ExchangeResolver.load_exchange(
request.param, exchange_conf, validate=True, load_leverage_tiers=True)
exchange_conf, validate=True, load_leverage_tiers=True)
yield exchange, request.param
+89 -20
View File
@@ -20,7 +20,7 @@ from freqtrade.exchange import (Binance, Bittrex, Exchange, Kraken, amount_to_pr
timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date,
timeframe_to_prev_date, timeframe_to_seconds)
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT,
calculate_backoff, remove_credentials)
calculate_backoff, remove_exchange_credentials)
from freqtrade.exchange.exchange import amount_to_contract_precision
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from tests.conftest import (EXMS, generate_test_data_raw, get_mock_coro, get_patched_exchange,
@@ -137,16 +137,14 @@ def test_init(default_conf, mocker, caplog):
assert log_has('Instance is running with dry_run enabled', caplog)
def test_remove_credentials(default_conf, caplog) -> None:
def test_remove_exchange_credentials(default_conf) -> None:
conf = deepcopy(default_conf)
conf['dry_run'] = False
remove_credentials(conf)
remove_exchange_credentials(conf['exchange'], False)
assert conf['exchange']['key'] != ''
assert conf['exchange']['secret'] != ''
conf['dry_run'] = True
remove_credentials(conf)
remove_exchange_credentials(conf['exchange'], True)
assert conf['exchange']['key'] == ''
assert conf['exchange']['secret'] == ''
assert conf['exchange']['password'] == ''
@@ -228,27 +226,30 @@ def test_exchange_resolver(default_conf, mocker, caplog):
mocker.patch(f'{EXMS}.validate_timeframes')
mocker.patch(f'{EXMS}.validate_stakecurrency')
mocker.patch(f'{EXMS}.validate_pricing')
exchange = ExchangeResolver.load_exchange('zaif', default_conf)
default_conf['exchange']['name'] = 'zaif'
exchange = ExchangeResolver.load_exchange(default_conf)
assert isinstance(exchange, Exchange)
assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog)
caplog.clear()
exchange = ExchangeResolver.load_exchange('Bittrex', default_conf)
default_conf['exchange']['name'] = 'Bittrex'
exchange = ExchangeResolver.load_exchange(default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Bittrex)
assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.",
caplog)
caplog.clear()
exchange = ExchangeResolver.load_exchange('kraken', default_conf)
default_conf['exchange']['name'] = 'kraken'
exchange = ExchangeResolver.load_exchange(default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Kraken)
assert not isinstance(exchange, Binance)
assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.",
caplog)
exchange = ExchangeResolver.load_exchange('binance', default_conf)
default_conf['exchange']['name'] = 'binance'
exchange = ExchangeResolver.load_exchange(default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Binance)
assert not isinstance(exchange, Kraken)
@@ -257,7 +258,8 @@ def test_exchange_resolver(default_conf, mocker, caplog):
caplog)
# Test mapping
exchange = ExchangeResolver.load_exchange('binanceus', default_conf)
default_conf['exchange']['name'] = 'binanceus'
exchange = ExchangeResolver.load_exchange(default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Binance)
assert not isinstance(exchange, Kraken)
@@ -990,19 +992,20 @@ def test_validate_pricing(default_conf, mocker):
mocker.patch(f'{EXMS}.validate_timeframes')
mocker.patch(f'{EXMS}.validate_stakecurrency')
mocker.patch(f'{EXMS}.name', 'Binance')
ExchangeResolver.load_exchange('binance', default_conf)
default_conf['exchange']['name'] = 'binance'
ExchangeResolver.load_exchange(default_conf)
has.update({'fetchTicker': False})
with pytest.raises(OperationalException, match="Ticker pricing not available for .*"):
ExchangeResolver.load_exchange('binance', default_conf)
ExchangeResolver.load_exchange(default_conf)
has.update({'fetchTicker': True})
default_conf['exit_pricing']['use_order_book'] = True
ExchangeResolver.load_exchange('binance', default_conf)
ExchangeResolver.load_exchange(default_conf)
has.update({'fetchL2OrderBook': False})
with pytest.raises(OperationalException, match="Orderbook not available for .*"):
ExchangeResolver.load_exchange('binance', default_conf)
ExchangeResolver.load_exchange(default_conf)
has.update({'fetchL2OrderBook': True})
@@ -1011,7 +1014,7 @@ def test_validate_pricing(default_conf, mocker):
default_conf['margin_mode'] = MarginMode.ISOLATED
with pytest.raises(OperationalException, match="Ticker pricing not available for .*"):
ExchangeResolver.load_exchange('binance', default_conf)
ExchangeResolver.load_exchange(default_conf)
def test_validate_ordertypes(default_conf, mocker):
@@ -1091,12 +1094,13 @@ def test_validate_ordertypes_stop_advanced(default_conf, mocker, exchange_name,
'stoploss_on_exchange': True,
'stoploss_price_type': stopadv,
}
default_conf['exchange']['name'] = exchange_name
if expected:
ExchangeResolver.load_exchange(exchange_name, default_conf)
ExchangeResolver.load_exchange(default_conf)
else:
with pytest.raises(OperationalException,
match=r'On exchange stoploss price type is not supported for .*'):
ExchangeResolver.load_exchange(exchange_name, default_conf)
ExchangeResolver.load_exchange(default_conf)
def test_validate_order_types_not_in_config(default_conf, mocker):
@@ -1773,6 +1777,71 @@ def test_fetch_positions(default_conf, mocker, exchange_name):
"fetch_positions", "fetch_positions")
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_orders(default_conf, mocker, exchange_name, limit_order):
api_mock = MagicMock()
api_mock.fetch_orders = MagicMock(return_value=[
limit_order['buy'],
limit_order['sell'],
])
api_mock.fetch_open_orders = MagicMock(return_value=[limit_order['buy']])
api_mock.fetch_closed_orders = MagicMock(return_value=[limit_order['buy']])
mocker.patch(f'{EXMS}.exchange_has', return_value=True)
start_time = datetime.now(timezone.utc) - timedelta(days=5)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
# Not available in dry-run
assert exchange.fetch_orders('mocked', start_time) == []
assert api_mock.fetch_orders.call_count == 0
default_conf['dry_run'] = False
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
res = exchange.fetch_orders('mocked', start_time)
assert api_mock.fetch_orders.call_count == 1
assert api_mock.fetch_open_orders.call_count == 0
assert api_mock.fetch_closed_orders.call_count == 0
assert len(res) == 2
res = exchange.fetch_orders('mocked', start_time)
api_mock.fetch_orders.reset_mock()
def has_resp(_, endpoint):
if endpoint == 'fetchOrders':
return False
if endpoint == 'fetchClosedOrders':
return True
if endpoint == 'fetchOpenOrders':
return True
mocker.patch(f'{EXMS}.exchange_has', has_resp)
# happy path without fetchOrders
res = exchange.fetch_orders('mocked', start_time)
assert api_mock.fetch_orders.call_count == 0
assert api_mock.fetch_open_orders.call_count == 1
assert api_mock.fetch_closed_orders.call_count == 1
mocker.patch(f'{EXMS}.exchange_has', return_value=True)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
"fetch_orders", "fetch_orders", retries=1,
pair='mocked', since=start_time)
# Unhappy path - first fetch-orders call fails.
api_mock.fetch_orders = MagicMock(side_effect=ccxt.NotSupported())
api_mock.fetch_open_orders.reset_mock()
api_mock.fetch_closed_orders.reset_mock()
res = exchange.fetch_orders('mocked', start_time)
assert api_mock.fetch_orders.call_count == 1
assert api_mock.fetch_open_orders.call_count == 1
assert api_mock.fetch_closed_orders.call_count == 1
def test_fetch_trading_fees(default_conf, mocker):
api_mock = MagicMock()
tick = {
@@ -4932,7 +5001,7 @@ def test_get_maintenance_ratio_and_amt_exceptions(mocker, default_conf, leverage
exchange._leverage_tiers = leverage_tiers
with pytest.raises(
OperationalException,
DependencyException,
match='nominal value can not be lower than 0',
):
exchange.get_maintenance_ratio_and_amt('1000SHIB/USDT:USDT', -1)
+7
View File
@@ -1,3 +1,4 @@
import platform
from copy import deepcopy
from pathlib import Path
from typing import Any, Dict
@@ -14,6 +15,11 @@ from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver
from tests.conftest import get_patched_exchange
def is_mac() -> bool:
machine = platform.system()
return "Darwin" in machine
@pytest.fixture(scope="function")
def freqai_conf(default_conf, tmpdir):
freqaiconf = deepcopy(default_conf)
@@ -36,6 +42,7 @@ def freqai_conf(default_conf, tmpdir):
"identifier": "uniqe-id100",
"live_trained_timestamp": 0,
"data_kitchen_thread_count": 2,
"activate_tensorboard": False,
"feature_parameters": {
"include_timeframes": ["5m"],
"include_corr_pairlist": ["ADA/BTC"],
+5 -1
View File
@@ -12,6 +12,7 @@ from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
from tests.conftest import get_patched_exchange, log_has_re
from tests.freqai.conftest import (get_patched_data_kitchen, get_patched_freqai_strategy,
make_data_dictionary, make_unfiltered_dataframe)
from tests.freqai.test_freqai_interface import is_mac
@pytest.mark.parametrize(
@@ -173,6 +174,9 @@ def test_get_full_model_path(mocker, freqai_conf, model):
freqai_conf.update({"timerange": "20180110-20180130"})
freqai_conf.update({"strategy": "freqai_test_strat"})
if is_mac():
pytest.skip("Mac is confused during this test for unknown reasons")
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
exchange = get_patched_exchange(mocker, freqai_conf)
strategy.dp = DataProvider(freqai_conf, exchange)
@@ -188,7 +192,7 @@ def test_get_full_model_path(mocker, freqai_conf, model):
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
new_timerange = TimeRange.parse_timerange("20180120-20180130")
freqai.dk.set_paths('ADA/BTC', None)
freqai.extract_data_and_train_model(
new_timerange, "ADA/BTC", strategy, freqai.dk, data_load_timerange)
+26 -13
View File
@@ -15,7 +15,7 @@ from freqtrade.optimize.backtesting import Backtesting
from freqtrade.persistence import Trade
from freqtrade.plugins.pairlistmanager import PairListManager
from tests.conftest import EXMS, create_mock_trades, get_patched_exchange, log_has_re
from tests.freqai.conftest import (get_patched_freqai_strategy, make_rl_config,
from tests.freqai.conftest import (get_patched_freqai_strategy, is_mac, make_rl_config,
mock_pytorch_mlp_model_training_parameters)
@@ -28,29 +28,22 @@ def is_arm() -> bool:
return "arm" in machine or "aarch64" in machine
def is_mac() -> bool:
machine = platform.system()
return "Darwin" in machine
def can_run_model(model: str) -> None:
if (is_arm() or is_py11()) and "Catboost" in model:
if is_arm() and "Catboost" in model:
pytest.skip("CatBoost is not supported on ARM.")
is_pytorch_model = 'Reinforcement' in model or 'PyTorch' in model
if is_pytorch_model and is_mac() and not is_arm():
pytest.skip("Reinforcement learning / PyTorch module not available on intel based Mac OS.")
if is_pytorch_model and is_py11():
pytest.skip("Reinforcement learning / PyTorch currently not available on python 3.11.")
@pytest.mark.parametrize('model, pca, dbscan, float32, can_short, shuffle, buffer', [
('LightGBMRegressor', True, False, True, True, False, 0),
('XGBoostRegressor', False, True, False, True, False, 10),
('XGBoostRFRegressor', False, False, False, True, False, 0),
('CatboostRegressor', False, False, False, True, True, 0),
('PyTorchMLPRegressor', False, False, False, True, False, 0),
('PyTorchMLPRegressor', False, False, False, False, False, 0),
('PyTorchTransformerRegressor', False, False, False, False, False, 0),
('ReinforcementLearner', False, True, False, True, False, 0),
('ReinforcementLearner_multiproc', False, False, False, True, False, 0),
('ReinforcementLearner_test_3ac', False, False, False, False, False, 0),
@@ -61,6 +54,11 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
dbscan, float32, can_short, shuffle, buffer):
can_run_model(model)
test_tb = True
if is_mac():
test_tb = False
model_save_ext = 'joblib'
freqai_conf.update({"freqaimodel": model})
freqai_conf.update({"timerange": "20180110-20180130"})
@@ -82,10 +80,13 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models")
freqai_conf["freqai"]["rl_config"]["drop_ohlc_from_features"] = True
if 'PyTorchMLPRegressor' in model:
if 'PyTorch' in model:
model_save_ext = 'zip'
pytorch_mlp_mtp = mock_pytorch_mlp_model_training_parameters()
freqai_conf['freqai']['model_training_parameters'].update(pytorch_mlp_mtp)
if 'Transformer' in model:
# transformer model takes a window, unlike the MLP regressor
freqai_conf.update({"conv_width": 10})
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
exchange = get_patched_exchange(mocker, freqai_conf)
@@ -93,6 +94,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
strategy.freqai_info = freqai_conf.get("freqai", {})
freqai = strategy.freqai
freqai.live = True
freqai.activate_tensorboard = test_tb
freqai.can_short = can_short
freqai.dk = FreqaiDataKitchen(freqai_conf)
freqai.dk.live = True
@@ -228,6 +230,7 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
("XGBoostRegressor", 2, "freqai_test_strat"),
("CatboostRegressor", 2, "freqai_test_strat"),
("PyTorchMLPRegressor", 2, "freqai_test_strat"),
("PyTorchTransformerRegressor", 2, "freqai_test_strat"),
("ReinforcementLearner", 3, "freqai_rl_test_strat"),
("XGBoostClassifier", 2, "freqai_test_classifier"),
("LightGBMClassifier", 2, "freqai_test_classifier"),
@@ -237,6 +240,9 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
)
def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog):
can_run_model(model)
test_tb = True
if is_mac():
test_tb = False
freqai_conf.get("freqai", {}).update({"save_backtest_models": True})
freqai_conf['runmode'] = RunMode.BACKTEST
@@ -253,9 +259,12 @@ def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog)
if 'test_4ac' in model:
freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models")
if 'PyTorchMLP' in model:
if 'PyTorch' in model:
pytorch_mlp_mtp = mock_pytorch_mlp_model_training_parameters()
freqai_conf['freqai']['model_training_parameters'].update(pytorch_mlp_mtp)
if 'Transformer' in model:
# transformer model takes a window, unlike the MLP regressor
freqai_conf.update({"conv_width": 10})
freqai_conf.get("freqai", {}).get("feature_parameters", {}).update(
{"indicator_periods_candles": [2]})
@@ -266,6 +275,7 @@ def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog)
strategy.freqai_info = freqai_conf.get("freqai", {})
freqai = strategy.freqai
freqai.live = False
freqai.activate_tensorboard = test_tb
freqai.dk = FreqaiDataKitchen(freqai_conf)
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
@@ -277,6 +287,7 @@ def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog)
df[f'%-constant_{i}'] = i
metadata = {"pair": "LTC/BTC"}
freqai.dk.set_paths('LTC/BTC', None)
freqai.start_backtesting(df, metadata, freqai.dk, strategy)
model_folders = [x for x in freqai.dd.full_path.iterdir() if x.is_dir()]
@@ -434,6 +445,7 @@ def test_principal_component_analysis(mocker, freqai_conf):
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
new_timerange = TimeRange.parse_timerange("20180120-20180130")
freqai.dk.set_paths('ADA/BTC', None)
freqai.extract_data_and_train_model(
new_timerange, "ADA/BTC", strategy, freqai.dk, data_load_timerange)
@@ -467,6 +479,7 @@ def test_plot_feature_importance(mocker, freqai_conf):
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
new_timerange = TimeRange.parse_timerange("20180120-20180130")
freqai.dk.set_paths('ADA/BTC', None)
freqai.extract_data_and_train_model(
new_timerange, "ADA/BTC", strategy, freqai.dk, data_load_timerange)
@@ -18,6 +18,11 @@ class ReinforcementLearner_test_3ac(ReinforcementLearner):
"""
User can override any function in BaseRLEnv and gym.Env. Here the user
sets a custom reward based on profit and trade duration.
Warning!
This is function is a showcase of functionality designed to show as many possible
environment control features as possible. It is also designed to run quickly
on small computers. This is a benchmark, it is *not* for live production.
"""
def calculate_reward(self, action: int) -> float:
@@ -18,6 +18,11 @@ class ReinforcementLearner_test_4ac(ReinforcementLearner):
"""
User can override any function in BaseRLEnv and gym.Env. Here the user
sets a custom reward based on profit and trade duration.
Warning!
This is function is a showcase of functionality designed to show as many possible
environment control features as possible. It is also designed to run quickly
on small computers. This is a benchmark, it is *not* for live production.
"""
def calculate_reward(self, action: int) -> float:
+1 -1
View File
@@ -354,7 +354,7 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats')
mocker.patch('freqtrade.optimize.backtesting.show_backtest_results')
sbs = mocker.patch('freqtrade.optimize.backtesting.store_backtest_stats')
sbc = mocker.patch('freqtrade.optimize.backtesting.store_backtest_signal_candles')
sbc = mocker.patch('freqtrade.optimize.backtesting.store_backtest_analysis_results')
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC']))
+14 -13
View File
@@ -21,7 +21,7 @@ from freqtrade.optimize.optimize_reports import (_get_resample_from_period, gene
generate_periodic_breakdown_stats,
generate_strategy_comparison,
generate_trading_stats, show_sorted_pairlist,
store_backtest_signal_candles,
store_backtest_analysis_results,
store_backtest_stats, text_table_bt_results,
text_table_exit_reason, text_table_strategy)
from freqtrade.resolvers.strategy_resolver import StrategyResolver
@@ -232,17 +232,17 @@ def test_store_backtest_candles(testdatadir, mocker):
candle_dict = {'DefStrat': {'UNITTEST/BTC': pd.DataFrame()}}
# mock directory exporting
store_backtest_signal_candles(testdatadir, candle_dict, '2022_01_01_15_05_13')
store_backtest_analysis_results(testdatadir, candle_dict, {}, '2022_01_01_15_05_13')
assert dump_mock.call_count == 1
assert dump_mock.call_count == 2
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
assert str(dump_mock.call_args_list[0][0][0]).endswith('_signals.pkl')
dump_mock.reset_mock()
# mock file exporting
filename = Path(testdatadir / 'testresult')
store_backtest_signal_candles(filename, candle_dict, '2022_01_01_15_05_13')
assert dump_mock.call_count == 1
store_backtest_analysis_results(filename, candle_dict, {}, '2022_01_01_15_05_13')
assert dump_mock.call_count == 2
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
# result will be testdatadir / testresult-<timestamp>_signals.pkl
assert str(dump_mock.call_args_list[0][0][0]).endswith('_signals.pkl')
@@ -254,10 +254,11 @@ def test_write_read_backtest_candles(tmpdir):
candle_dict = {'DefStrat': {'UNITTEST/BTC': pd.DataFrame()}}
# test directory exporting
stored_file = store_backtest_signal_candles(Path(tmpdir), candle_dict, '2022_01_01_15_05_13')
scp = stored_file.open("rb")
pickled_signal_candles = joblib.load(scp)
scp.close()
sample_date = '2022_01_01_15_05_13'
store_backtest_analysis_results(Path(tmpdir), candle_dict, {}, sample_date)
stored_file = Path(tmpdir / f'backtest-result-{sample_date}_signals.pkl')
with stored_file.open("rb") as scp:
pickled_signal_candles = joblib.load(scp)
assert pickled_signal_candles.keys() == candle_dict.keys()
assert pickled_signal_candles['DefStrat'].keys() == pickled_signal_candles['DefStrat'].keys()
@@ -268,10 +269,10 @@ def test_write_read_backtest_candles(tmpdir):
# test file exporting
filename = Path(tmpdir / 'testresult')
stored_file = store_backtest_signal_candles(filename, candle_dict, '2022_01_01_15_05_13')
scp = stored_file.open("rb")
pickled_signal_candles = joblib.load(scp)
scp.close()
store_backtest_analysis_results(filename, candle_dict, {}, sample_date)
stored_file = Path(tmpdir / f'testresult-{sample_date}_signals.pkl')
with stored_file.open("rb") as scp:
pickled_signal_candles = joblib.load(scp)
assert pickled_signal_candles.keys() == candle_dict.keys()
assert pickled_signal_candles['DefStrat'].keys() == pickled_signal_candles['DefStrat'].keys()
+2 -2
View File
@@ -239,7 +239,7 @@ def test_interest(fee, exchange, is_short, lev, minutes, rate, interest,
stake_amount=20.0,
amount=30.0,
open_rate=2.0,
open_date=datetime.utcnow() - timedelta(minutes=minutes),
open_date=datetime.now(timezone.utc) - timedelta(minutes=minutes),
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange=exchange,
@@ -2063,7 +2063,7 @@ def test_trade_truncates_string_fields():
stake_amount=20.0,
amount=30.0,
open_rate=2.0,
open_date=datetime.utcnow() - timedelta(minutes=20),
open_date=datetime.now(timezone.utc) - timedelta(minutes=20),
fee_open=0.001,
fee_close=0.001,
exchange='binance',
+5 -5
View File
@@ -1,5 +1,5 @@
import random
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
import pytest
@@ -24,8 +24,8 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool,
stake_amount=0.01,
fee_open=fee,
fee_close=fee,
open_date=datetime.utcnow() - timedelta(minutes=min_ago_open or 200),
close_date=datetime.utcnow() - timedelta(minutes=min_ago_close or 30),
open_date=datetime.now(timezone.utc) - timedelta(minutes=min_ago_open or 200),
close_date=datetime.now(timezone.utc) - timedelta(minutes=min_ago_close or 30),
open_rate=open_rate,
is_open=is_open,
amount=0.01 / open_rate,
@@ -87,9 +87,9 @@ def test_protectionmanager(mocker, default_conf):
for handler in freqtrade.protections._protection_handlers:
assert handler.name in constants.AVAILABLE_PROTECTIONS
if not handler.has_global_stop:
assert handler.global_stop(datetime.utcnow(), '*') is None
assert handler.global_stop(datetime.now(timezone.utc), '*') is None
if not handler.has_local_stop:
assert handler.stop_per_pair('XRP/BTC', datetime.utcnow(), '*') is None
assert handler.stop_per_pair('XRP/BTC', datetime.now(timezone.utc), '*') is None
@pytest.mark.parametrize('timeframe,expected,protconf', [
+6 -7
View File
@@ -261,8 +261,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
assert isnan(fiat_profit_sum)
def test__rpc_timeunit_profit(default_conf_usdt, ticker, fee,
limit_buy_order, limit_sell_order, markets, mocker) -> None:
def test__rpc_timeunit_profit(default_conf_usdt, ticker, fee, markets, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
EXMS,
@@ -295,7 +294,7 @@ def test__rpc_timeunit_profit(default_conf_usdt, ticker, fee,
assert day['starting_balance'] in (pytest.approx(1062.37), pytest.approx(1066.46))
assert day['fiat_value'] in (0.0, )
# ensure first day is current date
assert str(days['data'][0]['date']) == str(datetime.utcnow().date())
assert str(days['data'][0]['date']) == str(datetime.now(timezone.utc).date())
# Try invalid data
with pytest.raises(RPCException, match=r'.*must be an integer greater than 0*'):
@@ -415,8 +414,8 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None:
assert pytest.approx(stats['profit_all_percent_mean']) == -57.86
assert pytest.approx(stats['profit_all_fiat']) == -85.205614098
assert stats['trade_count'] == 7
assert stats['first_trade_date'] == '2 days ago'
assert stats['latest_trade_date'] == '17 minutes ago'
assert stats['first_trade_humanized'] == '2 days ago'
assert stats['latest_trade_humanized'] == '17 minutes ago'
assert stats['avg_duration'] in ('0:17:40')
assert stats['best_pair'] == 'XRP/USDT'
assert stats['best_rate'] == 10.0
@@ -426,8 +425,8 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None:
MagicMock(side_effect=ExchangeError("Pair 'XRP/USDT' not available")))
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
assert stats['trade_count'] == 7
assert stats['first_trade_date'] == '2 days ago'
assert stats['latest_trade_date'] == '17 minutes ago'
assert stats['first_trade_humanized'] == '2 days ago'
assert stats['latest_trade_humanized'] == '17 minutes ago'
assert stats['avg_duration'] in ('0:17:40')
assert stats['best_pair'] == 'XRP/USDT'
assert stats['best_rate'] == 10.0
+147 -113
View File
@@ -21,11 +21,13 @@ from freqtrade.__init__ import __version__
from freqtrade.enums import CandleType, RunMode, State, TradingMode
from freqtrade.exceptions import DependencyException, ExchangeError, OperationalException
from freqtrade.loggers import setup_logging, setup_logging_pre
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.persistence import PairLocks, Trade
from freqtrade.rpc import RPC
from freqtrade.rpc.api_server import ApiServer
from freqtrade.rpc.api_server.api_auth import create_token, get_user_from_token
from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer
from freqtrade.rpc.api_server.webserver_bgwork import ApiBG
from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades, get_mock_coro,
get_patched_freqtradebot, log_has, log_has_re, patch_get_signal)
@@ -601,7 +603,7 @@ def test_api_daily(botclient, mocker, ticker, fee, markets):
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.utcnow().date())
assert rc.json()['data'][0]['date'] == str(datetime.now(timezone.utc).date())
@pytest.mark.parametrize('is_short', [True, False])
@@ -740,6 +742,33 @@ def test_api_delete_open_order(botclient, mocker, fee, markets, ticker, is_short
assert cancel_mock.call_count == 1
@pytest.mark.parametrize('is_short', [True, False])
def test_api_trade_reload_trade(botclient, mocker, fee, markets, ticker, is_short):
ftbot, client = botclient
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
stoploss_mock = MagicMock()
cancel_mock = MagicMock()
ftbot.handle_onexchange_order = MagicMock()
mocker.patch.multiple(
EXMS,
markets=PropertyMock(return_value=markets),
fetch_ticker=ticker,
cancel_order=cancel_mock,
cancel_stoploss_order=stoploss_mock,
)
rc = client_post(client, f"{BASE_URI}/trades/10/reload")
assert_response(rc, 502)
assert 'Could not find trade with id 10.' in rc.json()['error']
assert ftbot.handle_onexchange_order.call_count == 0
create_mock_trades(fee, is_short=is_short)
Trade.commit()
rc = client_post(client, f"{BASE_URI}/trades/5/reload")
assert ftbot.handle_onexchange_order.call_count == 1
def test_api_logs(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/logs")
@@ -861,8 +890,10 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected)
'best_pair_profit_ratio': expected['best_pair_profit_ratio'],
'best_rate': expected['best_rate'],
'first_trade_date': ANY,
'first_trade_humanized': ANY,
'first_trade_timestamp': ANY,
'latest_trade_date': '5 minutes ago',
'latest_trade_date': ANY,
'latest_trade_humanized': '5 minutes ago',
'latest_trade_timestamp': ANY,
'profit_all_coin': pytest.approx(expected['profit_all_coin']),
'profit_all_fiat': pytest.approx(expected['profit_all_fiat']),
@@ -1197,7 +1228,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
stake_amount=1,
open_rate=0.245441,
open_order_id="123456",
open_date=datetime.utcnow(),
open_date=datetime.now(timezone.utc),
is_open=False,
is_short=False,
fee_close=fee.return_value,
@@ -1659,137 +1690,140 @@ def test_sysinfo(botclient):
def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir):
ftbot, client = botclient
mocker.patch(f'{EXMS}.get_fee', fee)
try:
ftbot, client = botclient
mocker.patch(f'{EXMS}.get_fee', fee)
rc = client_get(client, f"{BASE_URI}/backtest")
# Backtest prevented in default mode
assert_response(rc, 502)
rc = client_get(client, f"{BASE_URI}/backtest")
# Backtest prevented in default mode
assert_response(rc, 502)
ftbot.config['runmode'] = RunMode.WEBSERVER
# Backtesting not started yet
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
ftbot.config['runmode'] = RunMode.WEBSERVER
# Backtesting not started yet
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'not_started'
assert not result['running']
assert result['status_msg'] == 'Backtest not yet executed'
assert result['progress'] == 0
result = rc.json()
assert result['status'] == 'not_started'
assert not result['running']
assert result['status_msg'] == 'Backtest not yet executed'
assert result['progress'] == 0
# Reset backtesting
rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'reset'
assert not result['running']
assert result['status_msg'] == 'Backtest reset'
ftbot.config['export'] = 'trades'
ftbot.config['backtest_cache'] = 'day'
ftbot.config['user_data_dir'] = Path(tmpdir)
ftbot.config['exportfilename'] = Path(tmpdir) / "backtest_results"
ftbot.config['exportfilename'].mkdir()
# Reset backtesting
rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'reset'
assert not result['running']
assert result['status_msg'] == 'Backtest reset'
ftbot.config['export'] = 'trades'
ftbot.config['backtest_cache'] = 'day'
ftbot.config['user_data_dir'] = Path(tmpdir)
ftbot.config['exportfilename'] = Path(tmpdir) / "backtest_results"
ftbot.config['exportfilename'].mkdir()
# start backtesting
data = {
"strategy": CURRENT_TEST_STRATEGY,
"timeframe": "5m",
"timerange": "20180110-20180111",
"max_open_trades": 3,
"stake_amount": 100,
"dry_run_wallet": 1000,
"enable_protections": False
}
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc)
result = rc.json()
# start backtesting
data = {
"strategy": CURRENT_TEST_STRATEGY,
"timeframe": "5m",
"timerange": "20180110-20180111",
"max_open_trades": 3,
"stake_amount": 100,
"dry_run_wallet": 1000,
"enable_protections": False
}
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc)
result = rc.json()
assert result['status'] == 'running'
assert result['progress'] == 0
assert result['running']
assert result['status_msg'] == 'Backtest started'
assert result['status'] == 'running'
assert result['progress'] == 0
assert result['running']
assert result['status_msg'] == 'Backtest started'
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'ended'
assert not result['running']
assert result['status_msg'] == 'Backtest ended'
assert result['progress'] == 1
assert result['backtest_result']
result = rc.json()
assert result['status'] == 'ended'
assert not result['running']
assert result['status_msg'] == 'Backtest ended'
assert result['progress'] == 1
assert result['backtest_result']
rc = client_get(client, f"{BASE_URI}/backtest/abort")
assert_response(rc)
result = rc.json()
assert result['status'] == 'not_running'
assert not result['running']
assert result['status_msg'] == 'Backtest ended'
rc = client_get(client, f"{BASE_URI}/backtest/abort")
assert_response(rc)
result = rc.json()
assert result['status'] == 'not_running'
assert not result['running']
assert result['status_msg'] == 'Backtest ended'
# Simulate running backtest
ApiServer._bgtask_running = True
rc = client_get(client, f"{BASE_URI}/backtest/abort")
assert_response(rc)
result = rc.json()
assert result['status'] == 'stopping'
assert not result['running']
assert result['status_msg'] == 'Backtest ended'
# Simulate running backtest
ApiBG.bgtask_running = True
rc = client_get(client, f"{BASE_URI}/backtest/abort")
assert_response(rc)
result = rc.json()
assert result['status'] == 'stopping'
assert not result['running']
assert result['status_msg'] == 'Backtest ended'
# Get running backtest...
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'running'
assert result['running']
assert result['step'] == "backtest"
assert result['status_msg'] == "Backtest running"
# Get running backtest...
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'running'
assert result['running']
assert result['step'] == "backtest"
assert result['status_msg'] == "Backtest running"
# Try delete with task still running
rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'running'
# Try delete with task still running
rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'running'
# Post to backtest that's still running
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc, 502)
result = rc.json()
assert 'Bot Background task already running' in result['error']
# Post to backtest that's still running
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc, 502)
result = rc.json()
assert 'Bot Background task already running' in result['error']
ApiServer._bgtask_running = False
ApiBG.bgtask_running = False
# Rerun backtest (should get previous result)
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc)
result = rc.json()
assert log_has_re('Reusing result of previous backtest.*', caplog)
# Rerun backtest (should get previous result)
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc)
result = rc.json()
assert log_has_re('Reusing result of previous backtest.*', caplog)
data['stake_amount'] = 101
data['stake_amount'] = 101
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy',
side_effect=DependencyException('DeadBeef'))
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert log_has("Backtesting caused an error: DeadBeef", caplog)
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy',
side_effect=DependencyException('DeadBeef'))
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert log_has("Backtesting caused an error: DeadBeef", caplog)
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'error'
assert 'Backtest failed' in result['status_msg']
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'error'
assert 'Backtest failed' in result['status_msg']
# Delete backtesting to avoid leakage since the backtest-object may stick around.
rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc)
# Delete backtesting to avoid leakage since the backtest-object may stick around.
rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'reset'
assert not result['running']
assert result['status_msg'] == 'Backtest reset'
result = rc.json()
assert result['status'] == 'reset'
assert not result['running']
assert result['status_msg'] == 'Backtest reset'
# Disallow base64 strategies
data['strategy'] = "xx:cHJpbnQoImhlbGxvIHdvcmxkIik="
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc, 500)
# Disallow base64 strategies
data['strategy'] = "xx:cHJpbnQoImhlbGxvIHdvcmxkIik="
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc, 500)
finally:
Backtesting.cleanup()
def test_api_backtest_history(botclient, mocker, testdatadir):
+32 -9
View File
@@ -52,7 +52,7 @@ def default_conf(default_conf) -> dict:
@pytest.fixture
def update():
message = Message(0, datetime.utcnow(), Chat(0, 0))
message = Message(0, datetime.now(timezone.utc), Chat(0, 0))
_update = Update(0, message=message)
return _update
@@ -143,8 +143,8 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], "
"['balance'], ['start'], ['stop'], "
"['forceexit', 'forcesell', 'fx'], ['forcebuy', 'forcelong'], ['forceshort'], "
"['trades'], ['delete'], ['cancel_open_order', 'coo'], ['performance'], "
"['buys', 'entries'], ['exits', 'sells'], ['mix_tags'], "
"['reload_trade'], ['trades'], ['delete'], ['cancel_open_order', 'coo'], "
"['performance'], ['buys', 'entries'], ['exits', 'sells'], ['mix_tags'], "
"['stats'], ['daily'], ['weekly'], ['monthly'], "
"['count'], ['locks'], ['delete_locks', 'unlock'], "
"['reload_conf', 'reload_config'], ['show_conf', 'show_config'], "
@@ -213,7 +213,7 @@ async def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> Non
patch_exchange(mocker)
caplog.set_level(logging.DEBUG)
chat = Chat(0xdeadbeef, 0)
message = Message(randint(1, 100), datetime.utcnow(), chat)
message = Message(randint(1, 100), datetime.now(timezone.utc), chat)
update = Update(randint(1, 100), message=message)
default_conf['telegram']['enabled'] = False
@@ -520,7 +520,7 @@ async def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time
assert msg_mock.call_count == 1
assert "Daily Profit over the last 2 days</b>:" in msg_mock.call_args_list[0][0][0]
assert 'Day ' in msg_mock.call_args_list[0][0][0]
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
assert str(datetime.now(timezone.utc).date()) in msg_mock.call_args_list[0][0][0]
assert ' 6.83 USDT' in msg_mock.call_args_list[0][0][0]
assert ' 7.51 USD' in msg_mock.call_args_list[0][0][0]
assert '(2)' in msg_mock.call_args_list[0][0][0]
@@ -533,8 +533,9 @@ async def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time
await telegram._daily(update=update, context=context)
assert msg_mock.call_count == 1
assert "Daily Profit over the last 7 days</b>:" in msg_mock.call_args_list[0][0][0]
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
assert str((datetime.utcnow() - timedelta(days=5)).date()) in msg_mock.call_args_list[0][0][0]
assert str(datetime.now(timezone.utc).date()) in msg_mock.call_args_list[0][0][0]
assert str((datetime.now(timezone.utc) - timedelta(days=5)).date()
) in msg_mock.call_args_list[0][0][0]
assert ' 6.83 USDT' in msg_mock.call_args_list[0][0][0]
assert ' 7.51 USD' in msg_mock.call_args_list[0][0][0]
assert '(2)' in msg_mock.call_args_list[0][0][0]
@@ -608,7 +609,7 @@ async def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, tim
assert "Weekly Profit over the last 2 weeks (starting from Monday)</b>:" \
in msg_mock.call_args_list[0][0][0]
assert 'Monday ' in msg_mock.call_args_list[0][0][0]
today = datetime.utcnow().date()
today = datetime.now(timezone.utc).date()
first_iso_day_of_current_week = today - timedelta(days=today.weekday())
assert str(first_iso_day_of_current_week) in msg_mock.call_args_list[0][0][0]
assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0]
@@ -677,7 +678,7 @@ async def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, ti
assert msg_mock.call_count == 1
assert 'Monthly Profit over the last 2 months</b>:' in msg_mock.call_args_list[0][0][0]
assert 'Month ' in msg_mock.call_args_list[0][0][0]
today = datetime.utcnow().date()
today = datetime.now(timezone.utc).date()
current_month = f"{today.year}-{today.month:02} "
assert current_month in msg_mock.call_args_list[0][0][0]
assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0]
@@ -825,6 +826,9 @@ async def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_shor
assert 'Exit Reason' in msg_mock.call_args_list[-1][0][0]
assert 'ROI' in msg_mock.call_args_list[-1][0][0]
assert 'Avg. Duration' in msg_mock.call_args_list[-1][0][0]
# Duration is not only N/A
assert '0:19:00' in msg_mock.call_args_list[-1][0][0]
assert 'N/A' in msg_mock.call_args_list[-1][0][0]
msg_mock.reset_mock()
@@ -1760,6 +1764,25 @@ async def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short
assert "Please make sure to take care of this asset" in msg_mock.call_args_list[0][0][0]
@pytest.mark.parametrize('is_short', [True, False])
async def test_telegram_reload_trade_from_exchange(mocker, update, default_conf, fee, is_short):
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
context = MagicMock()
context.args = []
await telegram._reload_trade_from_exchange(update=update, context=context)
assert "Trade-id not set." in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
create_mock_trades(fee, is_short=is_short)
context.args = [5]
await telegram._reload_trade_from_exchange(update=update, context=context)
assert "Status: `Reloaded from orders from exchange`" in msg_mock.call_args_list[0][0][0]
@pytest.mark.parametrize('is_short', [True, False])
async def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short, ticker):
+3 -3
View File
@@ -1,4 +1,4 @@
from datetime import datetime
from datetime import datetime, timezone
import pytest
from pandas import DataFrame
@@ -43,12 +43,12 @@ def test_strategy_test_v3(dataframe_1m, fee, is_short, side):
assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1,
rate=20000, time_in_force='gtc',
current_time=datetime.utcnow(),
current_time=datetime.now(timezone.utc),
side=side, entry_tag=None) is True
assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1,
rate=20000, time_in_force='gtc', exit_reason='roi',
sell_reason='roi',
current_time=datetime.utcnow(),
current_time=datetime.now(timezone.utc),
side=side) is True
assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(),
+2 -2
View File
@@ -1271,7 +1271,7 @@ def test_pairlist_resolving_with_config_pl_not_exists(mocker, default_conf):
configuration.get_config()
def test_pairlist_resolving_fallback(mocker):
def test_pairlist_resolving_fallback(mocker, tmpdir):
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
mocker.patch.object(Path, "open", MagicMock(return_value=MagicMock()))
mocker.patch("freqtrade.configuration.configuration.load_file",
@@ -1290,7 +1290,7 @@ def test_pairlist_resolving_fallback(mocker):
assert config['pairs'] == ['ETH/BTC', 'XRP/BTC']
assert config['exchange']['name'] == 'binance'
assert config['datadir'] == Path.cwd() / "user_data/data/binance"
assert config['datadir'] == Path(tmpdir) / "user_data/data/binance"
@pytest.mark.parametrize("setting", [
+75 -2
View File
@@ -121,7 +121,7 @@ def test_order_dict(default_conf_usdt, mocker, runmode, caplog) -> None:
freqtrade = FreqtradeBot(conf)
if runmode == RunMode.LIVE:
assert not log_has_re(".*stoploss_on_exchange .* dry-run", caplog)
assert not log_has_re(r".*stoploss_on_exchange .* dry-run", caplog)
assert freqtrade.strategy.order_types['stoploss_on_exchange']
caplog.clear()
@@ -136,7 +136,7 @@ def test_order_dict(default_conf_usdt, mocker, runmode, caplog) -> None:
}
freqtrade = FreqtradeBot(conf)
assert not freqtrade.strategy.order_types['stoploss_on_exchange']
assert not log_has_re(".*stoploss_on_exchange .* dry-run", caplog)
assert not log_has_re(r".*stoploss_on_exchange .* dry-run", caplog)
def test_get_trade_stake_amount(default_conf_usdt, mocker) -> None:
@@ -149,6 +149,34 @@ def test_get_trade_stake_amount(default_conf_usdt, mocker) -> None:
assert result == default_conf_usdt['stake_amount']
@pytest.mark.parametrize('runmode', [
RunMode.DRY_RUN,
RunMode.LIVE
])
def test_load_strategy_no_keys(default_conf_usdt, mocker, runmode, caplog) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
conf = deepcopy(default_conf_usdt)
conf['runmode'] = runmode
erm = mocker.patch('freqtrade.freqtradebot.ExchangeResolver.load_exchange')
freqtrade = FreqtradeBot(conf)
strategy_config = freqtrade.strategy.config
assert id(strategy_config['exchange']) == id(conf['exchange'])
# Keys have been removed and are not passed to the exchange
assert strategy_config['exchange']['key'] == ''
assert strategy_config['exchange']['secret'] == ''
assert erm.call_count == 1
ex_conf = erm.call_args_list[0][1]['exchange_config']
assert id(ex_conf) != id(conf['exchange'])
# Keys are still present
assert ex_conf['key'] != ''
assert ex_conf['key'] == default_conf_usdt['exchange']['key']
assert ex_conf['secret'] != ''
assert ex_conf['secret'] == default_conf_usdt['exchange']['secret']
@pytest.mark.parametrize("amend_last,wallet,max_open,lsamr,expected", [
(False, 120, 2, 0.5, [60, None]),
(True, 120, 2, 0.5, [60, 58.8]),
@@ -5552,6 +5580,51 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
assert log_has(f"Error updating {order['id']}.", caplog)
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_onexchange_order(mocker, default_conf_usdt, limit_order, is_short, caplog):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mock_uts = mocker.spy(freqtrade, 'update_trade_state')
entry_order = limit_order[entry_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
mock_fo = mocker.patch(f'{EXMS}.fetch_orders', return_value=[
entry_order,
exit_order,
])
order_id = entry_order['id']
trade = Trade(
open_order_id=order_id,
pair='ETH/USDT',
fee_open=0.001,
fee_close=0.001,
open_rate=entry_order['price'],
open_date=arrow.utcnow().datetime,
stake_amount=entry_order['cost'],
amount=entry_order['amount'],
exchange="binance",
is_short=is_short,
leverage=1,
)
trade.orders.append(Order.parse_from_ccxt_object(
entry_order, 'ADA/USDT', entry_side(is_short))
)
Trade.session.add(trade)
freqtrade.handle_onexchange_order(trade)
assert log_has_re(r"Found previously unknown order .*", caplog)
assert mock_uts.call_count == 1
assert mock_fo.call_count == 1
trade = Trade.session.scalars(select(Trade)).first()
assert len(trade.orders) == 2
assert trade.is_open is False
assert trade.exit_reason == ExitType.SOLD_ON_EXCHANGE.value
def test_get_valid_price(mocker, default_conf_usdt) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
+3 -2
View File
@@ -75,8 +75,9 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
_notify_exit=MagicMock(),
)
mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock())
mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=1000))
wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update")
mocker.patch("freqtrade.wallets.Wallets.get_free", return_value=1000)
mocker.patch("freqtrade.wallets.Wallets.check_exit_amount", return_value=True)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
+3 -4
View File
@@ -1,5 +1,4 @@
from copy import deepcopy
from pathlib import Path
from unittest.mock import MagicMock
import pandas as pd
@@ -282,13 +281,13 @@ def test_generate_Plot_filename():
assert fn == "freqtrade-plot-UNITTEST_BTC-5m.html"
def test_generate_plot_file(mocker, caplog):
def test_generate_plot_file(mocker, caplog, user_dir):
fig = generate_empty_figure()
plot_mock = mocker.patch("freqtrade.plot.plotting.plot", MagicMock())
store_plot_file(fig, filename="freqtrade-plot-UNITTEST_BTC-5m.html",
directory=Path("user_data/plot"))
directory=user_dir / "plot")
expected_fn = str(Path("user_data/plot/freqtrade-plot-UNITTEST_BTC-5m.html"))
expected_fn = str(user_dir / "plot/freqtrade-plot-UNITTEST_BTC-5m.html")
assert plot_mock.call_count == 1
assert plot_mock.call_args[0][0] == fig
assert (plot_mock.call_args_list[0][1]['filename']
+6 -6
View File
@@ -16,18 +16,18 @@ if sys.version_info < (3, 9):
pytest.skip("StrategyUpdater is not compatible with Python 3.8", allow_module_level=True)
def test_strategy_updater_start(tmpdir, capsys) -> None:
def test_strategy_updater_start(user_dir, capsys) -> None:
# Effective test without mocks.
teststrats = Path(__file__).parent / 'strategy/strats'
tmpdirp = Path(tmpdir) / "strategies"
tmpdirp.mkdir()
tmpdirp = Path(user_dir) / "strategies"
tmpdirp.mkdir(parents=True, exist_ok=True)
shutil.copy(teststrats / 'strategy_test_v2.py', tmpdirp)
old_code = (teststrats / 'strategy_test_v2.py').read_text()
args = [
"strategy-updater",
"--userdir",
str(tmpdir),
str(user_dir),
"--strategy-list",
"StrategyTestV2"
]
@@ -36,9 +36,9 @@ def test_strategy_updater_start(tmpdir, capsys) -> None:
start_strategy_update(pargs)
assert Path(tmpdir / "strategies_orig_updater").exists()
assert Path(user_dir / "strategies_orig_updater").exists()
# Backup file exists
assert Path(tmpdir / "strategies_orig_updater" / 'strategy_test_v2.py').exists()
assert Path(user_dir / "strategies_orig_updater" / 'strategy_test_v2.py').exists()
# updated file exists
new_file = Path(tmpdirp / 'strategy_test_v2.py')
assert new_file.exists()
+47
View File
@@ -3,9 +3,11 @@ from copy import deepcopy
from unittest.mock import MagicMock
import pytest
from sqlalchemy import select
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
from freqtrade.exceptions import DependencyException
from freqtrade.persistence import Trade
from tests.conftest import EXMS, create_mock_trades, get_patched_freqtradebot, patch_wallet
@@ -364,3 +366,48 @@ def test_sync_wallet_futures_dry(mocker, default_conf, fee):
free = freqtrade.wallets.get_free('BTC')
used = freqtrade.wallets.get_used('BTC')
assert free + used == total
def test_check_exit_amount(mocker, default_conf, fee):
freqtrade = get_patched_freqtradebot(mocker, default_conf)
update_mock = mocker.patch("freqtrade.wallets.Wallets.update")
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=123)
create_mock_trades(fee, is_short=None)
trade = Trade.session.scalars(select(Trade)).first()
assert trade.amount == 123
assert freqtrade.wallets.check_exit_amount(trade) is True
assert update_mock.call_count == 0
assert total_mock.call_count == 1
update_mock.reset_mock()
# Reduce returned amount to below the trade amount - which should
# trigger a wallet update and return False, triggering "order refinding"
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=100)
assert freqtrade.wallets.check_exit_amount(trade) is False
assert update_mock.call_count == 1
assert total_mock.call_count == 2
def test_check_exit_amount_futures(mocker, default_conf, fee):
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
freqtrade = get_patched_freqtradebot(mocker, default_conf)
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=123)
create_mock_trades(fee, is_short=None)
trade = Trade.session.scalars(select(Trade)).first()
trade.trading_mode = 'futures'
assert trade.amount == 123
assert freqtrade.wallets.check_exit_amount(trade) is True
assert total_mock.call_count == 0
update_mock = mocker.patch("freqtrade.wallets.Wallets.update")
trade.amount = 150
# Reduce returned amount to below the trade amount - which should
# trigger a wallet update and return False, triggering "order refinding"
assert freqtrade.wallets.check_exit_amount(trade) is False
assert total_mock.call_count == 0
assert update_mock.call_count == 1