From 2403a03fcbbae135b9919e635ef8d63257610c97 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 29 Aug 2022 06:28:53 +0200 Subject: [PATCH 001/186] Version bump 2022.8 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 2572c03f1..6c5c52a04 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2022.8.dev' +__version__ = '2022.8' if 'dev' in __version__: try: From d0456b698cb2ed8f60d8a3b80165c36b657bf59c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 Sep 2022 07:22:41 +0200 Subject: [PATCH 002/186] Version bump 2022.9 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 6c5c52a04..634377e05 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2022.8' +__version__ = '2022.9' if 'dev' in __version__: try: From 8e101a9f1c3d0b76ef7be383644f62b2cf178d41 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Oct 2022 09:35:21 +0200 Subject: [PATCH 003/186] Disable log spam from analyze_df in webhook/discord --- freqtrade/rpc/discord.py | 2 +- freqtrade/rpc/webhook.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/discord.py b/freqtrade/rpc/discord.py index 9efe6f427..c48508300 100644 --- a/freqtrade/rpc/discord.py +++ b/freqtrade/rpc/discord.py @@ -30,9 +30,9 @@ class Discord(Webhook): pass def send_msg(self, msg) -> None: - logger.info(f"Sending discord message: {msg}") if msg['type'].value in self.config['discord']: + logger.info(f"Sending discord message: {msg}") msg['strategy'] = self.strategy msg['timeframe'] = self.timeframe diff --git a/freqtrade/rpc/webhook.py b/freqtrade/rpc/webhook.py index 6109e80bc..bb3b3922f 100644 --- a/freqtrade/rpc/webhook.py +++ b/freqtrade/rpc/webhook.py @@ -61,6 +61,14 @@ class Webhook(RPCHandler): RPCMessageType.STARTUP, RPCMessageType.WARNING): valuedict = whconfig.get('webhookstatus') + elif msg['type'] in ( + RPCMessageType.PROTECTION_TRIGGER, + RPCMessageType.PROTECTION_TRIGGER_GLOBAL, + RPCMessageType.WHITELIST, + RPCMessageType.ANALYZED_DF, + RPCMessageType.STRATEGY_MSG): + # Don't fail for non-implemented types + return else: raise NotImplementedError('Unknown message type: {}'.format(msg['type'])) if not valuedict: From 6841bdaa8193278d6863131f8866f4a59286da37 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Oct 2022 09:45:58 +0200 Subject: [PATCH 004/186] Update test to verify webhook won't log-spam on new messagetypes --- tests/rpc/test_rpc_webhook.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/rpc/test_rpc_webhook.py b/tests/rpc/test_rpc_webhook.py index 4d65b4966..3bbb85d54 100644 --- a/tests/rpc/test_rpc_webhook.py +++ b/tests/rpc/test_rpc_webhook.py @@ -365,6 +365,14 @@ def test_exception_send_msg(default_conf, mocker, caplog): with pytest.raises(NotImplementedError): webhook.send_msg(msg) + # Test no failure for not implemented but known messagetypes + for e in RPCMessageType: + msg = { + 'type': e, + 'status': 'whatever' + } + webhook.send_msg(msg) + def test__send_msg(default_conf, mocker, caplog): default_conf["webhook"] = get_webhook_dict() From 19b3669d971231654c3ff0ff5a52a6fe6a4e447e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Oct 2022 21:20:14 +0200 Subject: [PATCH 005/186] Decrease message throughput fixes memory leak by queue raising indefinitely --- freqtrade/rpc/api_server/webserver.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index df4324740..53af91477 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -198,8 +198,10 @@ class ApiServer(RPCHandler): logger.debug(f"Found message of type: {message.get('type')}") # Broadcast it await self._ws_channel_manager.broadcast(message) - # Sleep, make this configurable? - await asyncio.sleep(0.1) + # Limit messages per sec. + # Could cause problems with queue size if too low, and + # problems with network traffik if too high. + await asyncio.sleep(0.001) except asyncio.CancelledError: pass From 03256fc7768bfdebaa8ecbb9e547682ab36f0591 Mon Sep 17 00:00:00 2001 From: Robert Caulk Date: Sat, 1 Oct 2022 16:50:29 +0200 Subject: [PATCH 006/186] Merge pull request #7508 from aemr3/fix-pca-errors Fix feature list match for PCA --- .../freqai/base_models/BaseClassifierModel.py | 2 +- .../freqai/base_models/BaseRegressionModel.py | 2 +- freqtrade/freqai/data_drawer.py | 2 +- freqtrade/freqai/data_kitchen.py | 1 + freqtrade/freqai/freqai_interface.py | 17 +++++++++-------- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/freqtrade/freqai/base_models/BaseClassifierModel.py b/freqtrade/freqai/base_models/BaseClassifierModel.py index 70f212d2a..09f1bf98c 100644 --- a/freqtrade/freqai/base_models/BaseClassifierModel.py +++ b/freqtrade/freqai/base_models/BaseClassifierModel.py @@ -92,7 +92,7 @@ class BaseClassifierModel(IFreqaiModel): filtered_df = dk.normalize_data_from_metadata(filtered_df) dk.data_dictionary["prediction_features"] = filtered_df - self.data_cleaning_predict(dk, filtered_df) + self.data_cleaning_predict(dk) predictions = self.model.predict(dk.data_dictionary["prediction_features"]) pred_df = DataFrame(predictions, columns=dk.label_list) diff --git a/freqtrade/freqai/base_models/BaseRegressionModel.py b/freqtrade/freqai/base_models/BaseRegressionModel.py index 2450bf305..5d89dd356 100644 --- a/freqtrade/freqai/base_models/BaseRegressionModel.py +++ b/freqtrade/freqai/base_models/BaseRegressionModel.py @@ -92,7 +92,7 @@ class BaseRegressionModel(IFreqaiModel): dk.data_dictionary["prediction_features"] = filtered_df # optional additional data cleaning/analysis - self.data_cleaning_predict(dk, filtered_df) + self.data_cleaning_predict(dk) predictions = self.model.predict(dk.data_dictionary["prediction_features"]) pred_df = DataFrame(predictions, columns=dk.label_list) diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index 1839724f8..471f6875c 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -423,7 +423,7 @@ class FreqaiDataDrawer: dk.data["data_path"] = str(dk.data_path) dk.data["model_filename"] = str(dk.model_filename) - dk.data["training_features_list"] = list(dk.data_dictionary["train_features"].columns) + dk.data["training_features_list"] = dk.training_features_list dk.data["label_list"] = dk.label_list # store the metadata with open(save_path / f"{dk.model_filename}_metadata.json", "w") as fp: diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index f4fa4e5fd..697fd85cf 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -881,6 +881,7 @@ class FreqaiDataKitchen: """ column_names = dataframe.columns features = [c for c in column_names if "%" in c] + if not features: raise OperationalException("Could not find any features!") diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index d9f917338..78539bae5 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -275,7 +275,8 @@ class IFreqaiModel(ABC): if dk.check_if_backtest_prediction_exists(): self.dd.load_metadata(dk) - self.check_if_feature_list_matches_strategy(dataframe_train, dk) + dk.find_features(dataframe_train) + self.check_if_feature_list_matches_strategy(dk) append_df = dk.get_backtesting_prediction() dk.append_predictions(append_df) else: @@ -296,7 +297,6 @@ class IFreqaiModel(ABC): else: self.model = self.dd.load_data(pair, dk) - # self.check_if_feature_list_matches_strategy(dataframe_train, dk) pred_df, do_preds = self.predict(dataframe_backtest, dk) append_df = dk.get_predictions_to_append(pred_df, do_preds) dk.append_predictions(append_df) @@ -420,7 +420,7 @@ class IFreqaiModel(ABC): return def check_if_feature_list_matches_strategy( - self, dataframe: DataFrame, dk: FreqaiDataKitchen + self, dk: FreqaiDataKitchen ) -> None: """ Ensure user is passing the proper feature set if they are reusing an `identifier` pointing @@ -429,11 +429,12 @@ class IFreqaiModel(ABC): :param dk: FreqaiDataKitchen = non-persistent data container/analyzer for current coin/bot loop """ - dk.find_features(dataframe) + if "training_features_list_raw" in dk.data: feature_list = dk.data["training_features_list_raw"] else: feature_list = dk.data['training_features_list'] + if dk.training_features_list != feature_list: raise OperationalException( "Trying to access pretrained model with `identifier` " @@ -481,13 +482,16 @@ class IFreqaiModel(ABC): if self.freqai_info["feature_parameters"].get('noise_standard_deviation', 0): dk.add_noise_to_training_features() - def data_cleaning_predict(self, dk: FreqaiDataKitchen, dataframe: DataFrame) -> None: + def data_cleaning_predict(self, dk: FreqaiDataKitchen) -> None: """ Base data cleaning method for predict. Functions here are complementary to the functions of data_cleaning_train. """ ft_params = self.freqai_info["feature_parameters"] + # ensure user is feeding the correct indicators to the model + self.check_if_feature_list_matches_strategy(dk) + if ft_params.get('inlier_metric_window', 0): dk.compute_inlier_metric(set_='predict') @@ -505,9 +509,6 @@ class IFreqaiModel(ABC): if ft_params.get("use_DBSCAN_to_remove_outliers", False): dk.use_DBSCAN_to_remove_outliers(predict=True) - # ensure user is feeding the correct indicators to the model - self.check_if_feature_list_matches_strategy(dk.data_dictionary['prediction_features'], dk) - def model_exists(self, dk: FreqaiDataKitchen) -> bool: """ Given a pair and path, check if a model already exists From c53ff94b8e5de6a2cbbad69f386eb1c717211ce2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 30 Sep 2022 13:47:26 +0200 Subject: [PATCH 007/186] Force joblib update via setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0581081fa..d3f9ea7c0 100644 --- a/setup.py +++ b/setup.py @@ -72,7 +72,7 @@ setup( 'pandas', 'tables', 'blosc', - 'joblib', + 'joblib>=1.2.0', 'pyarrow; platform_machine != "armv7l"', 'fastapi', 'uvicorn', From 59cfde3767f449c588500acb07c12503ac3d9032 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 30 Sep 2022 15:43:05 +0200 Subject: [PATCH 008/186] Fix pandas deprecation warnings from freqAI --- freqtrade/freqai/data_kitchen.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 697fd85cf..766eb981f 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -210,7 +210,7 @@ class FreqaiDataKitchen: filtered_df = unfiltered_df.filter(training_feature_list, axis=1) filtered_df = filtered_df.replace([np.inf, -np.inf], np.nan) - drop_index = pd.isnull(filtered_df).any(1) # get the rows that have NaNs, + drop_index = pd.isnull(filtered_df).any(axis=1) # get the rows that have NaNs, drop_index = drop_index.replace(True, 1).replace(False, 0) # pep8 requirement. if (training_filter): const_cols = list((filtered_df.nunique() == 1).loc[lambda x: x].index) @@ -221,7 +221,7 @@ class FreqaiDataKitchen: # about removing any row with NaNs # if labels has multiple columns (user wants to train multiple modelEs), we detect here labels = unfiltered_df.filter(label_list, axis=1) - drop_index_labels = pd.isnull(labels).any(1) + drop_index_labels = pd.isnull(labels).any(axis=1) drop_index_labels = drop_index_labels.replace(True, 1).replace(False, 0) dates = unfiltered_df['date'] filtered_df = filtered_df[ @@ -249,7 +249,7 @@ class FreqaiDataKitchen: else: # we are backtesting so we need to preserve row number to send back to strategy, # so now we use do_predict to avoid any prediction based on a NaN - drop_index = pd.isnull(filtered_df).any(1) + drop_index = pd.isnull(filtered_df).any(axis=1) self.data["filter_drop_index_prediction"] = drop_index filtered_df.fillna(0, inplace=True) # replacing all NaNs with zeros to avoid issues in 'prediction', but any prediction @@ -808,7 +808,7 @@ class FreqaiDataKitchen: :, :no_prev_pts ] distances = distances.replace([np.inf, -np.inf], np.nan) - drop_index = pd.isnull(distances).any(1) + drop_index = pd.isnull(distances).any(axis=1) distances = distances[drop_index == 0] inliers = pd.DataFrame(index=distances.index) From 851d1e9da106aad2153ec38919873f61ce4b384f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 2 Oct 2022 06:56:02 +0200 Subject: [PATCH 009/186] Version bump 2022.9.1 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 634377e05..2bd09a377 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2022.9' +__version__ = '2022.9.1' if 'dev' in __version__: try: From ec7d6634963cd9482f47e5db0c6e90e9ffd176ba Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 28 Oct 2022 19:34:30 +0200 Subject: [PATCH 010/186] Version bump 2022.10 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index ce5f420e4..74c558a37 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2022.9.1' +__version__ = '2022.10' if 'dev' in __version__: try: From 5c571f565ff639d10b5095db7f7429e35a9c0b73 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 27 Nov 2022 15:34:00 +0100 Subject: [PATCH 011/186] Version bump 2022.11 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 74c558a37..47d095f65 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2022.10' +__version__ = '2022.11' if 'dev' in __version__: try: From c9bc91c75b8414d70bbe6291497d068f5b9d355e Mon Sep 17 00:00:00 2001 From: robcaulk Date: Fri, 16 Dec 2022 11:20:37 +0100 Subject: [PATCH 012/186] add shuffle_after_split option --- freqtrade/freqai/data_kitchen.py | 14 ++++++++++++++ tests/freqai/test_freqai_interface.py | 20 +++++++++++--------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 9c8158c8a..de6b74b21 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -1,5 +1,6 @@ import copy import logging +import random import shutil from datetime import datetime, timezone from math import cos, sin @@ -168,6 +169,19 @@ class FreqaiDataKitchen: train_labels = labels train_weights = weights + if feat_dict.get("shuffle_after_split", False): + rint1 = random.randint(0, 100) + rint2 = random.randint(0, 100) + train_features = train_features.sample( + frac=1, random_state=rint1).reset_index(drop=True) + train_labels = train_labels.sample(frac=1, random_state=rint1).reset_index(drop=True) + train_weights = pd.DataFrame(train_weights).sample( + frac=1, random_state=rint1).reset_index(drop=True).to_numpy()[:, 0] + test_features = test_features.sample(frac=1, random_state=rint2).reset_index(drop=True) + test_labels = test_labels.sample(frac=1, random_state=rint2).reset_index(drop=True) + test_weights = pd.DataFrame(test_weights).sample( + frac=1, random_state=rint2).reset_index(drop=True).to_numpy()[:, 0] + # Simplest way to reverse the order of training and test data: if self.freqai_config['feature_parameters'].get('reverse_train_test_order', False): return self.build_data_dictionary( diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index f19acb018..fde167823 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -27,16 +27,17 @@ def is_mac() -> bool: return "Darwin" in machine -@pytest.mark.parametrize('model, pca, dbscan, float32', [ - ('LightGBMRegressor', True, False, True), - ('XGBoostRegressor', False, True, False), - ('XGBoostRFRegressor', False, False, False), - ('CatboostRegressor', False, False, False), - ('ReinforcementLearner', False, True, False), - ('ReinforcementLearner_multiproc', False, False, False), - ('ReinforcementLearner_test_4ac', False, False, False) +@pytest.mark.parametrize('model, pca, dbscan, float32, shuffle', [ + ('LightGBMRegressor', True, False, True, False), + ('XGBoostRegressor', False, True, False, False), + ('XGBoostRFRegressor', False, False, False, False), + ('CatboostRegressor', False, False, False, True), + ('ReinforcementLearner', False, True, False, False), + ('ReinforcementLearner_multiproc', False, False, False, False), + ('ReinforcementLearner_test_4ac', False, False, False, False) ]) -def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, dbscan, float32): +def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, + dbscan, float32, shuffle): if is_arm() and model == 'CatboostRegressor': pytest.skip("CatBoost is not supported on ARM") @@ -50,6 +51,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, freqai_conf['freqai']['feature_parameters'].update({"principal_component_analysis": pca}) freqai_conf['freqai']['feature_parameters'].update({"use_DBSCAN_to_remove_outliers": dbscan}) freqai_conf.update({"reduce_df_footprint": float32}) + freqai_conf['freqai']['feature_parameters'].update({"shuffle_after_split": shuffle}) if 'ReinforcementLearner' in model: model_save_ext = 'zip' From 8e8f71ade560efe5364c6bc3238168ecd9631e04 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 28 Dec 2022 15:42:38 +0100 Subject: [PATCH 013/186] Version bump 2022.12 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index b44189cb0..4c0f570b1 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2022.12.dev' +__version__ = '2022.12' if 'dev' in __version__: try: From dc256684686f41c46527a88cdcc75496bd165612 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Wed, 4 Jan 2023 11:41:06 +0100 Subject: [PATCH 014/186] handle data gaps between FreqAI and DP better --- freqtrade/freqai/data_drawer.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index 848fb20eb..d8e08ed2d 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -645,12 +645,20 @@ class FreqaiDataDrawer: + 1 ) except IndexError: + index = -1 + if history_data[pair][tf].iloc[-1]['date'] < df_dp['date'].iloc[0]: + index = 0 + else: + index = -1 logger.warning( - f"Unable to update pair history for {pair}. " - "If this does not resolve itself after 1 additional candle, " - "please report the error to #freqai discord channel" + f"No common dates in historical data and dataprovider for {pair}. " + f"Appending dataprovider to historical data (full? {not bool(index)})" + "but please be aware that there is likely a gap in the historical " + "data.\n" + f"Historical data ends at {history_data[pair][tf].iloc[-1]['date']} " + f"while dataprovider starts at {df_dp['date'].iloc[0]} and" + f"ends at {df_dp['date'].iloc[0]}." ) - return history_data[pair][tf] = pd.concat( [ From 3cbe51c3caf50070bbe74e09d23c6161893d3cb7 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Wed, 4 Jan 2023 13:58:25 +0100 Subject: [PATCH 015/186] remove duplicated line --- freqtrade/freqai/data_drawer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index d8e08ed2d..1317ba6d3 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -645,7 +645,6 @@ class FreqaiDataDrawer: + 1 ) except IndexError: - index = -1 if history_data[pair][tf].iloc[-1]['date'] < df_dp['date'].iloc[0]: index = 0 else: From 786f7469586ae4705446bb8e6233fb22e3d32dd4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 30 Jan 2023 07:11:02 +0100 Subject: [PATCH 016/186] Version bump to 2023.1 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 18b6c9130..f531bb605 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2023.1.dev' +__version__ = '2023.1' if 'dev' in __version__: from pathlib import Path From a3cc001f1b38264ef6be1863378e709de102e325 Mon Sep 17 00:00:00 2001 From: Rahul Date: Sat, 11 Feb 2023 18:31:25 -0500 Subject: [PATCH 017/186] initial commit --- freqtrade/enums/marketstatetype.py | 26 ++++++++++++++++++++++++++ freqtrade/rpc/telegram.py | 20 ++++++++++++++++++-- freqtrade/strategy/interface.py | 5 ++++- 3 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 freqtrade/enums/marketstatetype.py diff --git a/freqtrade/enums/marketstatetype.py b/freqtrade/enums/marketstatetype.py new file mode 100644 index 000000000..5f3f219d8 --- /dev/null +++ b/freqtrade/enums/marketstatetype.py @@ -0,0 +1,26 @@ +from enum import Enum + + +class MarketDirection(Enum): + """ + Enum for various market directions. + """ + LONG = "long" + SHORT = "short" + EVEN = "even" + NONE = '' + + @staticmethod + def string_to_enum(label : str) -> str: + match label: + case "long": + return MarketDirection.LONG + case "short": + return MarketDirection.SHORT + case "even": + return MarketDirection.EVEN + case 'none': + return MarketDirection.NONE + case _: + return None + diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index fbd675d02..750f0dd5f 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -25,7 +25,7 @@ from telegram.utils.helpers import escape_markdown from freqtrade.__init__ import __version__ from freqtrade.constants import DUST_PER_COIN, Config -from freqtrade.enums import RPCMessageType, SignalDirection, TradingMode +from freqtrade.enums import MarketDirection, RPCMessageType, SignalDirection, TradingMode from freqtrade.exceptions import OperationalException from freqtrade.misc import chunks, plural, round_coin_value from freqtrade.persistence import Trade @@ -129,7 +129,7 @@ class Telegram(RPCHandler): r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', r'/forcebuy$', r'/forcelong$', r'/forceshort$', r'/forcesell$', r'/forceexit$', - r'/edge$', r'/health$', r'/help$', r'/version$' + r'/edge$', r'/health$', r'/help$', r'/version$', r'/marketdir$' ] # Create keys for generation valid_keys_print = [k.replace('$', '') for k in valid_keys] @@ -197,6 +197,7 @@ class Telegram(RPCHandler): CommandHandler('health', self._health), CommandHandler('help', self._help), CommandHandler('version', self._version), + CommandHandler('marketdir', self._changemarketdir) ] callbacks = [ CallbackQueryHandler(self._status_table, pattern='update_status_table'), @@ -1677,3 +1678,18 @@ class Telegram(RPCHandler): 'TelegramError: %s! Giving up on that message.', telegram_err.message ) + + @authorized_only + def _changemarketdir(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /marketdir. + Updates the bot's market_direction + :param bot: telegram bot + :param update: message update + :return: None + """ + if context.args and len(context.args) == 1: + market_dir = MarketDirection.string_to_enum(context.args[0]) + if market_dir: + self._rpc._freqtrade.strategy.market_direction = market_dir + diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 70d656199..96f8681e9 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -12,7 +12,7 @@ from pandas import DataFrame from freqtrade.constants import Config, IntOrInf, ListPairsWithTimeframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RunMode, SignalDirection, +from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, MarketDirection, RunMode, SignalDirection, SignalTagType, SignalType, TradingMode) from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds @@ -122,6 +122,9 @@ class IStrategy(ABC, HyperStrategyMixin): # Definition of plot_config. See plotting documentation for more details. plot_config: Dict = {} + # A self set parameter that represents the market direction. filled from configuration + market_direction: MarketDirection = MarketDirection.NONE + def __init__(self, config: Config) -> None: self.config = config # Dict to determine if analysis is necessary From be85ef2707fbcc3dd68964da7b1af791238d890a Mon Sep 17 00:00:00 2001 From: robcaulk Date: Thu, 16 Feb 2023 18:50:11 +0100 Subject: [PATCH 018/186] add documentation for shuffle_after_split, add to constants --- docs/freqai-parameter-table.md | 1 + freqtrade/constants.py | 3 ++- freqtrade/freqai/data_kitchen.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/freqai-parameter-table.md b/docs/freqai-parameter-table.md index 43a066fb8..328e7c0b5 100644 --- a/docs/freqai-parameter-table.md +++ b/docs/freqai-parameter-table.md @@ -45,6 +45,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | `noise_standard_deviation` | If set, FreqAI adds noise to the training features with the aim of preventing overfitting. FreqAI generates random deviates from a gaussian distribution with a standard deviation of `noise_standard_deviation` and adds them to all data points. `noise_standard_deviation` should be kept relative to the normalized space, i.e., between -1 and 1. In other words, since data in FreqAI is always normalized to be between -1 and 1, `noise_standard_deviation: 0.05` would result in 32% of the data being randomly increased/decreased by more than 2.5% (i.e., the percent of data falling within the first standard deviation).
**Datatype:** Integer.
Default: `0`. | `outlier_protection_percentage` | Enable to prevent outlier detection methods from discarding too much data. If more than `outlier_protection_percentage` % of points are detected as outliers by the SVM or DBSCAN, FreqAI will log a warning message and ignore outlier detection, i.e., the original dataset will be kept intact. If the outlier protection is triggered, no predictions will be made based on the training dataset.
**Datatype:** Float.
Default: `30`. | `reverse_train_test_order` | Split the feature dataset (see below) and use the latest data split for training and test on historical split of the data. This allows the model to be trained up to the most recent data point, while avoiding overfitting. However, you should be careful to understand the unorthodox nature of this parameter before employing it.
**Datatype:** Boolean.
Default: `False` (no reversal). +| `shuffle_after_split` | Split the data into train and test sets, and then shuffle both sets individually.
**Datatype:** Boolean.
Default: `False`. ### Data split parameters diff --git a/freqtrade/constants.py b/freqtrade/constants.py index b2e707d1a..a724664a4 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -568,7 +568,8 @@ CONF_SCHEMA = { "shuffle": {"type": "boolean", "default": False}, "nu": {"type": "number", "default": 0.1} }, - } + }, + "shuffle_after_split": {"type": "boolean", "default": False} }, "required": ["include_timeframes", "include_corr_pairlist", ] }, diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 9c7c9101c..30d2509b5 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -171,7 +171,7 @@ class FreqaiDataKitchen: train_labels = labels train_weights = weights - if feat_dict.get("shuffle_after_split", False): + if feat_dict["shuffle_after_split"]: rint1 = random.randint(0, 100) rint2 = random.randint(0, 100) train_features = train_features.sample( From 351c5fbf7f12319a46439b5b2c18fe8650a8e8c4 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Thu, 16 Feb 2023 19:48:22 +0100 Subject: [PATCH 019/186] add shuffle_after_split to conftest --- tests/freqai/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/freqai/conftest.py b/tests/freqai/conftest.py index bee7df27e..5e8945239 100644 --- a/tests/freqai/conftest.py +++ b/tests/freqai/conftest.py @@ -46,6 +46,7 @@ def freqai_conf(default_conf, tmpdir): "use_SVM_to_remove_outliers": True, "stratify_training_data": 0, "indicator_periods_candles": [10], + "shuffle_after_split": False }, "data_split_parameters": {"test_size": 0.33, "shuffle": False}, "model_training_parameters": {"n_estimators": 100}, From b73089deb82d15ea2db1780f9d1ebea709ce3daa Mon Sep 17 00:00:00 2001 From: Rahul Date: Thu, 16 Feb 2023 17:51:50 -0500 Subject: [PATCH 020/186] fixed a test --- freqtrade/enums/__init__.py | 1 + tests/rpc/test_rpc_telegram.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/enums/__init__.py b/freqtrade/enums/__init__.py index 8ef53e12d..160ebc052 100644 --- a/freqtrade/enums/__init__.py +++ b/freqtrade/enums/__init__.py @@ -12,3 +12,4 @@ from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODE from freqtrade.enums.signaltype import SignalDirection, SignalTagType, SignalType from freqtrade.enums.state import State from freqtrade.enums.tradingmode import TradingMode +from freqtrade.enums.marketstatetype import MarketDirection diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 5e3c2bd18..050d7b7c0 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -106,7 +106,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None: "['reload_config', 'reload_conf'], ['show_config', 'show_conf'], " "['stopbuy', 'stopentry'], ['whitelist'], ['blacklist'], " "['blacklist_delete', 'bl_delete'], " - "['logs'], ['edge'], ['health'], ['help'], ['version']" + "['logs'], ['edge'], ['health'], ['help'], ['version'], ['marketdir']" "]") assert log_has(message_str, caplog) From 72af1912caff5862938bd0c71eff4998314d6107 Mon Sep 17 00:00:00 2001 From: Rahul Date: Fri, 17 Feb 2023 22:01:00 +0000 Subject: [PATCH 021/186] added new text --- freqtrade/enums/__init__.py | 2 +- freqtrade/enums/marketstatetype.py | 15 --------------- freqtrade/rpc/telegram.py | 16 ++++++++++++---- freqtrade/strategy/interface.py | 4 ++-- tests/rpc/test_rpc_telegram.py | 14 +++++++++++++- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/freqtrade/enums/__init__.py b/freqtrade/enums/__init__.py index 160ebc052..69ef345e8 100644 --- a/freqtrade/enums/__init__.py +++ b/freqtrade/enums/__init__.py @@ -5,6 +5,7 @@ from freqtrade.enums.exitchecktuple import ExitCheckTuple from freqtrade.enums.exittype import ExitType from freqtrade.enums.hyperoptstate import HyperoptState from freqtrade.enums.marginmode import MarginMode +from freqtrade.enums.marketstatetype import MarketDirection from freqtrade.enums.ordertypevalue import OrderTypeValues from freqtrade.enums.pricetype import PriceType from freqtrade.enums.rpcmessagetype import NO_ECHO_MESSAGES, RPCMessageType, RPCRequestType @@ -12,4 +13,3 @@ from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODE from freqtrade.enums.signaltype import SignalDirection, SignalTagType, SignalType from freqtrade.enums.state import State from freqtrade.enums.tradingmode import TradingMode -from freqtrade.enums.marketstatetype import MarketDirection diff --git a/freqtrade/enums/marketstatetype.py b/freqtrade/enums/marketstatetype.py index 5f3f219d8..8132be74a 100644 --- a/freqtrade/enums/marketstatetype.py +++ b/freqtrade/enums/marketstatetype.py @@ -9,18 +9,3 @@ class MarketDirection(Enum): SHORT = "short" EVEN = "even" NONE = '' - - @staticmethod - def string_to_enum(label : str) -> str: - match label: - case "long": - return MarketDirection.LONG - case "short": - return MarketDirection.SHORT - case "even": - return MarketDirection.EVEN - case 'none': - return MarketDirection.NONE - case _: - return None - diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 750f0dd5f..60a5bcce6 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1689,7 +1689,15 @@ class Telegram(RPCHandler): :return: None """ if context.args and len(context.args) == 1: - market_dir = MarketDirection.string_to_enum(context.args[0]) - if market_dir: - self._rpc._freqtrade.strategy.market_direction = market_dir - + new_market_dir = context.args[0] + match new_market_dir: + case "long": + self._rpc._freqtrade.strategy.market_direction = MarketDirection.LONG + case "short": + self._rpc._freqtrade.strategy.market_direction = MarketDirection.SHORT + case "even": + self._rpc._freqtrade.strategy.market_direction = MarketDirection.EVEN + case "none": + self._rpc._freqtrade.strategy.market_direction = MarketDirection.NONE + case _: + raise RPCException("Invalid market direction provided") diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index d53f57d17..96b2ac8ce 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -12,8 +12,8 @@ from pandas import DataFrame from freqtrade.constants import Config, IntOrInf, ListPairsWithTimeframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, MarketDirection, RunMode, SignalDirection, - SignalTagType, SignalType, TradingMode) +from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, MarketDirection, RunMode, + SignalDirection, SignalTagType, SignalType, TradingMode) from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds from freqtrade.misc import remove_entry_exit_signals diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 050d7b7c0..a63d7f9be 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -20,7 +20,7 @@ from telegram.error import BadRequest, NetworkError, TelegramError from freqtrade import __version__ from freqtrade.constants import CANCEL_REASON from freqtrade.edge import PairInfo -from freqtrade.enums import ExitType, RPCMessageType, RunMode, SignalDirection, State +from freqtrade.enums import ExitType, MarketDirection, RPCMessageType, RunMode, SignalDirection, State from freqtrade.exceptions import OperationalException from freqtrade.freqtradebot import FreqtradeBot from freqtrade.loggers import setup_logging @@ -2394,3 +2394,15 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: assert log_has("using custom keyboard from config.json: " "[['/daily', '/stats', '/balance', '/profit', '/profit 5'], ['/count', " "'/start', '/reload_config', '/help']]", caplog) + + +def test_change_market_direction(default_conf, mocker, update) -> None: + telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) + assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.NONE + context = MagicMock() + context.args = ["long"] + telegram._changemarketdir(update, context) + assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.LONG + context = MagicMock() + context.args = ["invalid"] + assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.LONG From ade64f25d385d8fffda81b2e6199b7185bdc239a Mon Sep 17 00:00:00 2001 From: Rahul Gudise Date: Fri, 17 Feb 2023 17:08:39 -0500 Subject: [PATCH 022/186] fixed formatting --- tests/rpc/test_rpc_telegram.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index a63d7f9be..01a598734 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -20,7 +20,8 @@ from telegram.error import BadRequest, NetworkError, TelegramError from freqtrade import __version__ from freqtrade.constants import CANCEL_REASON from freqtrade.edge import PairInfo -from freqtrade.enums import ExitType, MarketDirection, RPCMessageType, RunMode, SignalDirection, State +from freqtrade.enums import (ExitType, MarketDirection, RPCMessageType, RunMode, SignalDirection, + State) from freqtrade.exceptions import OperationalException from freqtrade.freqtradebot import FreqtradeBot from freqtrade.loggers import setup_logging From 2c0fbd8500efaa1b388ac1410729aa793c398955 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 18 Feb 2023 18:03:07 +0100 Subject: [PATCH 023/186] Simplify test slightly --- tests/strategy/test_strategy_helpers.py | 73 +++++++++++++------------ 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index f42f9e681..36e997f7b 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -119,53 +119,54 @@ def test_merge_informative_pair_suffix_append_timeframe(): merge_informative_pair(data, informative, '15m', '1h', suffix="suf") -def test_stoploss_from_open(): +@pytest.mark.parametrize("side,profitrange", [ + # profit range for long is [-1, inf] while for shorts is [-inf, 1] + ("long", [-0.99, 2, 30]), + ("short", [-2.0, 0.99, 30]), +]) +def test_stoploss_from_open(side, profitrange): open_price_ranges = [ [0.01, 1.00, 30], [1, 100, 30], [100, 10000, 30], ] - # profit range for long is [-1, inf] while for shorts is [-inf, 1] - current_profit_range_dict = {'long': [-0.99, 2, 30], 'short': [-2.0, 0.99, 30]} - desired_stop_range = [-0.50, 0.50, 30] - for side, current_profit_range in current_profit_range_dict.items(): - for open_range in open_price_ranges: - for open_price in np.linspace(*open_range): - for desired_stop in np.linspace(*desired_stop_range): + for open_range in open_price_ranges: + for open_price in np.linspace(*open_range): + for desired_stop in np.linspace(-0.50, 0.50, 30): + if side == 'long': + # -1 is not a valid current_profit, should return 1 + assert stoploss_from_open(desired_stop, -1) == 1 + else: + # 1 is not a valid current_profit for shorts, should return 1 + assert stoploss_from_open(desired_stop, 1, True) == 1 + + for current_profit in np.linspace(*profitrange): if side == 'long': - # -1 is not a valid current_profit, should return 1 - assert stoploss_from_open(desired_stop, -1) == 1 + current_price = open_price * (1 + current_profit) + expected_stop_price = open_price * (1 + desired_stop) + stoploss = stoploss_from_open(desired_stop, current_profit) + stop_price = current_price * (1 - stoploss) else: - # 1 is not a valid current_profit for shorts, should return 1 - assert stoploss_from_open(desired_stop, 1, True) == 1 + current_price = open_price * (1 - current_profit) + expected_stop_price = open_price * (1 - desired_stop) + stoploss = stoploss_from_open(desired_stop, current_profit, True) + stop_price = current_price * (1 + stoploss) - for current_profit in np.linspace(*current_profit_range): - if side == 'long': - current_price = open_price * (1 + current_profit) - expected_stop_price = open_price * (1 + desired_stop) - stoploss = stoploss_from_open(desired_stop, current_profit) - stop_price = current_price * (1 - stoploss) - else: - current_price = open_price * (1 - current_profit) - expected_stop_price = open_price * (1 - desired_stop) - stoploss = stoploss_from_open(desired_stop, current_profit, True) - stop_price = current_price * (1 + stoploss) + assert stoploss >= 0 + # Technically the formula can yield values greater than 1 for shorts + # eventhough it doesn't make sense because the position would be liquidated + if side == 'long': + assert stoploss <= 1 - assert stoploss >= 0 - # Technically the formula can yield values greater than 1 for shorts - # eventhough it doesn't make sense because the position would be liquidated - if side == 'long': - assert stoploss <= 1 - - # there is no correct answer if the expected stop price is above - # the current price - if ((side == 'long' and expected_stop_price > current_price) - or (side == 'short' and expected_stop_price < current_price)): - assert stoploss == 0 - else: - assert pytest.approx(stop_price) == expected_stop_price + # there is no correct answer if the expected stop price is above + # the current price + if ((side == 'long' and expected_stop_price > current_price) + or (side == 'short' and expected_stop_price < current_price)): + assert stoploss == 0 + else: + assert pytest.approx(stop_price) == expected_stop_price def test_stoploss_from_absolute(): From f89b63b0c5fec77ac6db74a9e9034af69624bc56 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 18 Feb 2023 19:25:11 +0100 Subject: [PATCH 024/186] Fix dry-run stoploss orders filling "in place" after restart. --- freqtrade/persistence/trade_model.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 535067084..c84fcec9e 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -151,7 +151,7 @@ class Order(_DECL_BASE): self.order_update_date = datetime.now(timezone.utc) def to_ccxt_object(self) -> Dict[str, Any]: - return { + order = { 'id': self.order_id, 'symbol': self.ft_pair, 'price': self.price, @@ -169,6 +169,9 @@ class Order(_DECL_BASE): 'fee': None, 'info': {}, } + if self.ft_order_side == 'stoploss': + order['ft_order_type'] = 'stoploss' + return order def to_json(self, entry_side: str, minified: bool = False) -> Dict[str, Any]: resp = { From 5fb539190d99746765ad8d68b179d01ba93a11aa Mon Sep 17 00:00:00 2001 From: Rahul Date: Sat, 18 Feb 2023 23:50:02 +0000 Subject: [PATCH 025/186] addressed some issues mentioned in PR --- freqtrade/rpc/rpc.py | 5 ++++- freqtrade/rpc/telegram.py | 25 +++++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 83bffb779..10ea04c6c 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -20,7 +20,7 @@ from freqtrade.constants import CANCEL_REASON, DATETIME_PRINT_FORMAT, Config from freqtrade.data.history import load_data from freqtrade.data.metrics import calculate_max_drawdown from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, State, - TradingMode) + TradingMode, MarketDirection) from freqtrade.exceptions import ExchangeError, PricingError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs from freqtrade.loggers import bufferHandler @@ -1205,3 +1205,6 @@ class RPC: 'last_process_loc': last_p.astimezone(tzlocal()).strftime(DATETIME_PRINT_FORMAT), 'last_process_ts': int(last_p.timestamp()), } + + def _update_market_direction(self, direction: MarketDirection): + self._freqtrade.strategy.market_direction = direction \ No newline at end of file diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 60a5bcce6..65ef1bb21 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -129,7 +129,7 @@ class Telegram(RPCHandler): r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', r'/forcebuy$', r'/forcelong$', r'/forceshort$', r'/forcesell$', r'/forceexit$', - r'/edge$', r'/health$', r'/help$', r'/version$', r'/marketdir$' + r'/edge$', r'/health$', r'/help$', r'/version$', r'/marketdir \d+$' ] # Create keys for generation valid_keys_print = [k.replace('$', '') for k in valid_keys] @@ -1690,14 +1690,15 @@ class Telegram(RPCHandler): """ if context.args and len(context.args) == 1: new_market_dir = context.args[0] - match new_market_dir: - case "long": - self._rpc._freqtrade.strategy.market_direction = MarketDirection.LONG - case "short": - self._rpc._freqtrade.strategy.market_direction = MarketDirection.SHORT - case "even": - self._rpc._freqtrade.strategy.market_direction = MarketDirection.EVEN - case "none": - self._rpc._freqtrade.strategy.market_direction = MarketDirection.NONE - case _: - raise RPCException("Invalid market direction provided") + if new_market_dir == "long": + self._rpc._update_market_direction(MarketDirection.LONG) + elif new_market_dir == "short": + self._rpc._update_market_direction(MarketDirection.SHORT) + elif new_market_dir == "even": + self._rpc._update_market_direction(MarketDirection.EVEN) + elif new_market_dir == "none": + self._rpc._update_market_direction(MarketDirection.NONE) + else: + raise RPCException("Invalid market direction provided") + else: + raise RPCException("Invalid usage of command /marketdir.") From 8927a92eafc5d30ab106daec8bb43cfdaecd43d7 Mon Sep 17 00:00:00 2001 From: Rahul Date: Sun, 19 Feb 2023 16:11:21 +0000 Subject: [PATCH 026/186] fixed lint issue --- freqtrade/rpc/rpc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 10ea04c6c..f1e6c15e6 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -19,8 +19,8 @@ from freqtrade.configuration.timerange import TimeRange from freqtrade.constants import CANCEL_REASON, DATETIME_PRINT_FORMAT, Config from freqtrade.data.history import load_data from freqtrade.data.metrics import calculate_max_drawdown -from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, State, - TradingMode, MarketDirection) +from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, MarketDirection, SignalDirection, + State, TradingMode) from freqtrade.exceptions import ExchangeError, PricingError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs from freqtrade.loggers import bufferHandler @@ -1207,4 +1207,4 @@ class RPC: } def _update_market_direction(self, direction: MarketDirection): - self._freqtrade.strategy.market_direction = direction \ No newline at end of file + self._freqtrade.strategy.market_direction = direction From e9c64c583945a499edf49a5abc1b69c60ee22d2f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 19 Feb 2023 19:30:27 +0100 Subject: [PATCH 027/186] Update dependency to cysystemd closes #8187 --- docs/advanced-setup.md | 2 +- freqtrade/loggers.py | 2 +- tests/conftest.py | 2 +- tests/test_configuration.py | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index 93a2025ed..54d489aa1 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -192,7 +192,7 @@ $RepeatedMsgReduction on ### Logging to journald -This needs the `systemd` python package installed as the dependency, which is not available on Windows. Hence, the whole journald logging functionality is not available for a bot running on Windows. +This needs the `cysystemd` python package installed as dependency (`pip install cysystemd`), which is not available on Windows. Hence, the whole journald logging functionality is not available for a bot running on Windows. To send Freqtrade log messages to `journald` system service use the `--logfile` command line option with the value in the following format: diff --git a/freqtrade/loggers.py b/freqtrade/loggers.py index f365053c9..ff002f5d6 100644 --- a/freqtrade/loggers.py +++ b/freqtrade/loggers.py @@ -103,7 +103,7 @@ def setup_logging(config: Config) -> None: logging.root.addHandler(handler_sl) elif s[0] == 'journald': # pragma: no cover try: - from systemd.journal import JournaldLogHandler + from cysystemd.journal import JournaldLogHandler except ImportError: raise OperationalException("You need the systemd python package be installed in " "order to use logging to journald.") diff --git a/tests/conftest.py b/tests/conftest.py index 06a86c3b3..c74b1f0f1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2573,7 +2573,7 @@ def import_fails() -> None: realimport = builtins.__import__ def mockedimport(name, *args, **kwargs): - if name in ["filelock", 'systemd.journal', 'uvloop']: + if name in ["filelock", 'cysystemd.journal', 'uvloop']: raise ImportError(f"No module named '{name}'") return realimport(name, *args, **kwargs) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index a2a1b72cc..abf7be5d1 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -701,15 +701,16 @@ def test_set_loggers_journald(mocker): 'logfile': 'journald', } + setup_logging_pre() setup_logging(config) - assert len(logger.handlers) == 2 + assert len(logger.handlers) == 3 assert [x for x in logger.handlers if type(x).__name__ == "JournaldLogHandler"] assert [x for x in logger.handlers if type(x) == logging.StreamHandler] # reset handlers to not break pytest logger.handlers = orig_handlers -def test_set_loggers_journald_importerror(mocker, import_fails): +def test_set_loggers_journald_importerror(import_fails): logger = logging.getLogger() orig_handlers = logger.handlers logger.handlers = [] From 2ef656fac04842b87018b6587ab512045b998d24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Feb 2023 03:56:50 +0000 Subject: [PATCH 028/186] Bump scipy from 1.10.0 to 1.10.1 Bumps [scipy](https://github.com/scipy/scipy) from 1.10.0 to 1.10.1. - [Release notes](https://github.com/scipy/scipy/releases) - [Commits](https://github.com/scipy/scipy/compare/v1.10.0...v1.10.1) --- updated-dependencies: - dependency-name: scipy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 171ede929..904b5d661 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -2,7 +2,7 @@ -r requirements.txt # Required for hyperopt -scipy==1.10.0 +scipy==1.10.1 scikit-learn==1.1.3 scikit-optimize==0.9.0 filelock==3.9.0 From f19128ad21eeb07652ab8000c13f65a11c140342 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Feb 2023 03:56:55 +0000 Subject: [PATCH 029/186] Bump fastapi from 0.91.0 to 0.92.0 Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.91.0 to 0.92.0. - [Release notes](https://github.com/tiangolo/fastapi/releases) - [Commits](https://github.com/tiangolo/fastapi/compare/0.91.0...0.92.0) --- updated-dependencies: - dependency-name: fastapi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 38591b6dd..7b6fe157a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -36,7 +36,7 @@ orjson==3.8.6 sdnotify==0.3.2 # API Server -fastapi==0.91.0 +fastapi==0.92.0 pydantic==1.10.4 uvicorn==0.20.0 pyjwt==2.6.0 From c85fc6c8cad03323afbc68c7e373294911ca1aa8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Feb 2023 03:57:10 +0000 Subject: [PATCH 030/186] Bump mkdocs-material from 9.0.12 to 9.0.13 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.0.12 to 9.0.13. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.0.12...9.0.13) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 8d99e3e58..f5e671e88 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.3.7 mkdocs==1.4.2 -mkdocs-material==9.0.12 +mkdocs-material==9.0.13 mdx_truly_sane_lists==1.3 pymdown-extensions==9.9.2 jinja2==3.1.2 From a4e69574d35e0994acb063c728da56a233191465 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Feb 2023 03:57:17 +0000 Subject: [PATCH 031/186] Bump ccxt from 2.7.93 to 2.8.17 Bumps [ccxt](https://github.com/ccxt/ccxt) from 2.7.93 to 2.8.17. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/2.7.93...2.8.17) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 38591b6dd..8a62519d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.24.2 pandas==1.5.3 pandas-ta==0.3.14b -ccxt==2.7.93 +ccxt==2.8.17 # Pin cryptography for now due to rust build errors with piwheels cryptography==38.0.1; platform_machine == 'armv7l' cryptography==39.0.1; platform_machine != 'armv7l' From eb08ef6cedcd4faf74544d20159e8bb92d196602 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Feb 2023 03:57:41 +0000 Subject: [PATCH 032/186] Bump types-requests from 2.28.11.12 to 2.28.11.13 Bumps [types-requests](https://github.com/python/typeshed) from 2.28.11.12 to 2.28.11.13. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 83604b897..fc2d52d77 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -28,6 +28,6 @@ nbconvert==7.2.9 # mypy types types-cachetools==5.3.0.0 types-filelock==3.2.7 -types-requests==2.28.11.12 +types-requests==2.28.11.13 types-tabulate==0.9.0.0 types-python-dateutil==2.8.19.6 From 0cd28e2cabb686246718d716b6a100509779cc9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Feb 2023 03:58:03 +0000 Subject: [PATCH 033/186] Bump mypy from 1.0.0 to 1.0.1 Bumps [mypy](https://github.com/python/mypy) from 1.0.0 to 1.0.1. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v1.0.0...v1.0.1) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 83604b897..75582be72 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,7 +9,7 @@ coveralls==3.3.1 flake8==6.0.0 flake8-tidy-imports==4.8.0 -mypy==1.0.0 +mypy==1.0.1 pre-commit==3.0.4 pytest==7.2.1 pytest-asyncio==0.20.3 From 250faf012dd2fd0b1b1ad3d71cce04869bd0bf94 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 Feb 2023 06:55:58 +0100 Subject: [PATCH 034/186] Bump types-requests for pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 529140572..57ce81b8c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: additional_dependencies: - types-cachetools==5.3.0.0 - types-filelock==3.2.7 - - types-requests==2.28.11.12 + - types-requests==2.28.11.13 - types-tabulate==0.9.0.0 - types-python-dateutil==2.8.19.6 # stages: [push] From 789c867c8f56c8747eb9f62a2782d2725d928466 Mon Sep 17 00:00:00 2001 From: Achmad Fathoni Date: Mon, 20 Feb 2023 16:30:23 +0700 Subject: [PATCH 035/186] Fix outdated systemd related exception text. --- freqtrade/loggers.py | 2 +- tests/test_configuration.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/loggers.py b/freqtrade/loggers.py index ff002f5d6..823fa174e 100644 --- a/freqtrade/loggers.py +++ b/freqtrade/loggers.py @@ -105,7 +105,7 @@ def setup_logging(config: Config) -> None: try: from cysystemd.journal import JournaldLogHandler except ImportError: - raise OperationalException("You need the systemd python package be installed in " + raise OperationalException("You need the cysystemd python package be installed in " "order to use logging to journald.") handler_jd = get_existing_handlers(JournaldLogHandler) if handler_jd: diff --git a/tests/test_configuration.py b/tests/test_configuration.py index abf7be5d1..4a94a3c2e 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -719,7 +719,7 @@ def test_set_loggers_journald_importerror(import_fails): 'logfile': 'journald', } with pytest.raises(OperationalException, - match=r'You need the systemd python package.*'): + match=r'You need the cysystemd python package.*'): setup_logging(config) logger.handlers = orig_handlers From 3033e274660df0fa1c0eb445b0d03a459eefcdb3 Mon Sep 17 00:00:00 2001 From: Rahul Gudise Date: Mon, 20 Feb 2023 15:53:29 -0500 Subject: [PATCH 036/186] Added documentation for new telegram command --- docs/telegram-usage.md | 9 ++++++++- freqtrade/rpc/telegram.py | 4 +++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 4626944c5..03bbb4b87 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -152,7 +152,7 @@ You can create your own keyboard in `config.json`: !!! Note "Supported Commands" Only the following commands are allowed. Command arguments are not supported! - `/start`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopentry`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/edge`, `/help`, `/version` + `/start`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopentry`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/edge`, `/help`, `/version`, `/marketdir` ## Telegram commands @@ -179,6 +179,7 @@ official commands. You can ask at any moment for help with `/help`. | `/count` | Displays number of trades used and available | `/locks` | Show currently locked pairs. | `/unlock ` | Remove the lock for this pair (or for this lock id). +| `/marketdir [long | short | even | none]` | Updates the user managed variable that represents the current market direction. | **Modify Trade states** | | `/forceexit | /fx ` | Instantly exits the given trade (Ignoring `minimum_roi`). | `/forceexit all | /fx all` | Instantly exits all open trades (Ignoring `minimum_roi`). @@ -416,3 +417,9 @@ ARDR/ETH 0.366667 0.143059 -0.01 ### /version > **Version:** `0.14.3` + +### /marketdir + +Updates the user managed variable that represents the current market direction. This variable is not set +to any market direction on bot startup and must be set by the user. For example `/marketdir long` +would set the variable to be `long`. diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 65ef1bb21..5f682b436 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -129,7 +129,7 @@ class Telegram(RPCHandler): r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', r'/forcebuy$', r'/forcelong$', r'/forceshort$', r'/forcesell$', r'/forceexit$', - r'/edge$', r'/health$', r'/help$', r'/version$', r'/marketdir \d+$' + r'/edge$', r'/health$', r'/help$', r'/version$', r'/marketdir (long|short|even|none)$' ] # Create keys for generation valid_keys_print = [k.replace('$', '') for k in valid_keys] @@ -1495,6 +1495,8 @@ class Telegram(RPCHandler): "*/count:* `Show number of active trades compared to allowed number of trades`\n" "*/edge:* `Shows validated pairs by Edge if it is enabled` \n" "*/health* `Show latest process timestamp - defaults to 1970-01-01 00:00:00` \n" + "*/marketdir [long | short | even | none]:* `Updates the user managed variable" + " that represents the current market direction` \n" "_Statistics_\n" "------------\n" From 2261cbd92e180779f5e234d9fb5713c33b0b1e82 Mon Sep 17 00:00:00 2001 From: Rahul Gudise Date: Mon, 20 Feb 2023 16:22:17 -0500 Subject: [PATCH 037/186] fixed command regex and updated documentation --- docs/telegram-usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 03bbb4b87..a4145df02 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -421,5 +421,5 @@ ARDR/ETH 0.366667 0.143059 -0.01 ### /marketdir Updates the user managed variable that represents the current market direction. This variable is not set -to any market direction on bot startup and must be set by the user. For example `/marketdir long` +to any valid market direction on bot startup and must be set by the user. As an example `/marketdir long` would set the variable to be `long`. From fd4e27d889c4b4d192373e86b4b4a591dab8783e Mon Sep 17 00:00:00 2001 From: robcaulk Date: Tue, 21 Feb 2023 14:22:40 +0100 Subject: [PATCH 038/186] remove populate_any_indicators --- freqtrade/freqai/data_kitchen.py | 139 ++++++------------------ freqtrade/freqai/freqai_interface.py | 32 +----- freqtrade/optimize/backtesting.py | 2 +- tests/freqai/test_freqai_backtesting.py | 4 +- tests/strategy/test_interface.py | 12 -- 5 files changed, 41 insertions(+), 148 deletions(-) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 30d2509b5..ba304aca3 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -1315,123 +1315,54 @@ class FreqaiDataKitchen: dataframe: DataFrame = dataframe containing populated indicators """ - # this is a hack to check if the user is using the populate_any_indicators function + # check if the user is using the deprecated populate_any_indicators function new_version = inspect.getsource(strategy.populate_any_indicators) == ( inspect.getsource(IStrategy.populate_any_indicators)) - if new_version: - tfs: List[str] = self.freqai_config["feature_parameters"].get("include_timeframes") - pairs: List[str] = self.freqai_config["feature_parameters"].get( - "include_corr_pairlist", []) + if not new_version: + raise OperationalException( + "You are using the `populate_any_indicators()` function" + " which was deprecated on March 1, 2023. Please refer " + "to the strategy migration guide to use the new " + "feature_engineering_* methods: \n" + "https://www.freqtrade.io/en/stable/strategy_migration/#freqai-strategy \n" + "And the feature_engineering_* documentation: \n" + "https://www.freqtrade.io/en/latest/freqai-feature-engineering/" + ) - for tf in tfs: - if tf not in base_dataframes: - base_dataframes[tf] = pd.DataFrame() - for p in pairs: - if p not in corr_dataframes: - corr_dataframes[p] = {} - if tf not in corr_dataframes[p]: - corr_dataframes[p][tf] = pd.DataFrame() - - if not prediction_dataframe.empty: - dataframe = prediction_dataframe.copy() - else: - dataframe = base_dataframes[self.config["timeframe"]].copy() - - corr_pairs: List[str] = self.freqai_config["feature_parameters"].get( - "include_corr_pairlist", []) - dataframe = self.populate_features(dataframe.copy(), pair, strategy, - corr_dataframes, base_dataframes) - metadata = {"pair": pair} - dataframe = strategy.feature_engineering_standard(dataframe.copy(), metadata=metadata) - # ensure corr pairs are always last - for corr_pair in corr_pairs: - if pair == corr_pair: - continue # dont repeat anything from whitelist - if corr_pairs and do_corr_pairs: - dataframe = self.populate_features(dataframe.copy(), corr_pair, strategy, - corr_dataframes, base_dataframes, True) - - dataframe = strategy.set_freqai_targets(dataframe.copy(), metadata=metadata) - - self.get_unique_classes_from_labels(dataframe) - - dataframe = self.remove_special_chars_from_feature_names(dataframe) - - if self.config.get('reduce_df_footprint', False): - dataframe = reduce_dataframe_footprint(dataframe) - - return dataframe - - else: - # the user is using the populate_any_indicators functions which is deprecated - - df = self.use_strategy_to_populate_indicators_old_version( - strategy, corr_dataframes, base_dataframes, pair, - prediction_dataframe, do_corr_pairs) - return df - - def use_strategy_to_populate_indicators_old_version( - self, - strategy: IStrategy, - corr_dataframes: dict = {}, - base_dataframes: dict = {}, - pair: str = "", - prediction_dataframe: DataFrame = pd.DataFrame(), - do_corr_pairs: bool = True, - ) -> DataFrame: - """ - Use the user defined strategy for populating indicators during retrain - :param strategy: IStrategy = user defined strategy object - :param corr_dataframes: dict = dict containing the df pair dataframes - (for user defined timeframes) - :param base_dataframes: dict = dict containing the current pair dataframes - (for user defined timeframes) - :param metadata: dict = strategy furnished pair metadata - :return: - dataframe: DataFrame = dataframe containing populated indicators - """ - - # for prediction dataframe creation, we let dataprovider handle everything in the strategy - # so we create empty dictionaries, which allows us to pass None to - # `populate_any_indicators()`. Signaling we want the dp to give us the live dataframe. tfs: List[str] = self.freqai_config["feature_parameters"].get("include_timeframes") - pairs: List[str] = self.freqai_config["feature_parameters"].get("include_corr_pairlist", []) + pairs: List[str] = self.freqai_config["feature_parameters"].get( + "include_corr_pairlist", []) + + for tf in tfs: + if tf not in base_dataframes: + base_dataframes[tf] = pd.DataFrame() + for p in pairs: + if p not in corr_dataframes: + corr_dataframes[p] = {} + if tf not in corr_dataframes[p]: + corr_dataframes[p][tf] = pd.DataFrame() + if not prediction_dataframe.empty: dataframe = prediction_dataframe.copy() - for tf in tfs: - base_dataframes[tf] = None - for p in pairs: - if p not in corr_dataframes: - corr_dataframes[p] = {} - corr_dataframes[p][tf] = None else: dataframe = base_dataframes[self.config["timeframe"]].copy() - sgi = False - for tf in tfs: - if tf == tfs[-1]: - sgi = True # doing this last allows user to use all tf raw prices in labels - dataframe = strategy.populate_any_indicators( - pair, - dataframe.copy(), - tf, - informative=base_dataframes[tf], - set_generalized_indicators=sgi - ) - + corr_pairs: List[str] = self.freqai_config["feature_parameters"].get( + "include_corr_pairlist", []) + dataframe = self.populate_features(dataframe.copy(), pair, strategy, + corr_dataframes, base_dataframes) + metadata = {"pair": pair} + dataframe = strategy.feature_engineering_standard(dataframe.copy(), metadata=metadata) # ensure corr pairs are always last - for corr_pair in pairs: + for corr_pair in corr_pairs: if pair == corr_pair: continue # dont repeat anything from whitelist - for tf in tfs: - if pairs and do_corr_pairs: - dataframe = strategy.populate_any_indicators( - corr_pair, - dataframe.copy(), - tf, - informative=corr_dataframes[corr_pair][tf] - ) + if corr_pairs and do_corr_pairs: + dataframe = self.populate_features(dataframe.copy(), corr_pair, strategy, + corr_dataframes, base_dataframes, True) + + dataframe = strategy.set_freqai_targets(dataframe.copy(), metadata=metadata) self.get_unique_classes_from_labels(dataframe) diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index c265e42f9..c7b39b4e8 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -1,4 +1,3 @@ -import inspect import logging import threading import time @@ -106,8 +105,6 @@ class IFreqaiModel(ABC): self.max_system_threads = max(int(psutil.cpu_count() * 2 - 2), 1) self.can_short = True # overridden in start() with strategy.can_short - self.warned_deprecated_populate_any_indicators = False - record_params(config, self.full_path) def __getstate__(self): @@ -138,9 +135,6 @@ class IFreqaiModel(ABC): self.data_provider = strategy.dp self.can_short = strategy.can_short - # check if the strategy has deprecated populate_any_indicators function - self.check_deprecated_populate_any_indicators(strategy) - if self.live: self.inference_timer('start') self.dk = FreqaiDataKitchen(self.config, self.live, metadata["pair"]) @@ -489,7 +483,7 @@ class IFreqaiModel(ABC): "strategy is furnishing the same features as the pretrained" "model. In case of --strategy-list, please be aware that FreqAI " "requires all strategies to maintain identical " - "populate_any_indicator() functions" + "feature_engineering_* functions" ) def data_cleaning_train(self, dk: FreqaiDataKitchen) -> None: @@ -601,7 +595,7 @@ class IFreqaiModel(ABC): :param strategy: IStrategy = user defined strategy object :param dk: FreqaiDataKitchen = non-persistent data container for current coin/loop :param data_load_timerange: TimeRange = the amount of data to be loaded - for populate_any_indicators + for populating indicators (larger than new_trained_timerange so that new_trained_timerange does not contain any NaNs) """ @@ -806,7 +800,7 @@ class IFreqaiModel(ABC): logger.warning("Couldn't cache corr_pair dataframes for improved performance. " "Consider ensuring that the full coin/stake, e.g. XYZ/USD, " "is included in the column names when you are creating features " - "in `populate_any_indicators()`.") + "in `feature_engineering_*` functions.") self.get_corr_dataframes = not bool(self.corr_dataframes) elif self.corr_dataframes: dataframe = dk.attach_corr_pair_columns( @@ -933,26 +927,6 @@ class IFreqaiModel(ABC): dk.return_dataframe, saved_dataframe, how='left', left_on='date', right_on="date_pred") return dk - def check_deprecated_populate_any_indicators(self, strategy: IStrategy): - """ - Check and warn if the deprecated populate_any_indicators function is used. - :param strategy: strategy object - """ - - if not self.warned_deprecated_populate_any_indicators: - self.warned_deprecated_populate_any_indicators = True - old_version = inspect.getsource(strategy.populate_any_indicators) != ( - inspect.getsource(IStrategy.populate_any_indicators)) - - if old_version: - logger.warning("DEPRECATION WARNING: " - "You are using the deprecated populate_any_indicators function. " - "This function will raise an error on March 1 2023. " - "Please update your strategy by using " - "the new feature_engineering functions. See \n" - "https://www.freqtrade.io/en/latest/freqai-feature-engineering/" - "for details.") - # Following methods which are overridden by user made prediction models. # See freqai/prediction_models/CatboostPredictionModel.py for an example. diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 065a88f40..023be9a1a 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -93,7 +93,7 @@ class Backtesting: if self.config.get('strategy_list'): if self.config.get('freqai', {}).get('enabled', False): logger.warning("Using --strategy-list with FreqAI REQUIRES all strategies " - "to have identical populate_any_indicators.") + "to have identical feature_engineering_* functions.") for strat in list(self.config['strategy_list']): stratconf = deepcopy(self.config) stratconf['strategy'] = strat diff --git a/tests/freqai/test_freqai_backtesting.py b/tests/freqai/test_freqai_backtesting.py index 60963e762..0a8059966 100644 --- a/tests/freqai/test_freqai_backtesting.py +++ b/tests/freqai/test_freqai_backtesting.py @@ -35,8 +35,8 @@ def test_freqai_backtest_start_backtest_list(freqai_conf, mocker, testdatadir, c args = get_args(args) bt_config = setup_optimize_configuration(args, RunMode.BACKTEST) Backtesting(bt_config) - assert log_has_re('Using --strategy-list with FreqAI REQUIRES all strategies to have identical ' - 'populate_any_indicators.', caplog) + assert log_has_re('Using --strategy-list with FreqAI REQUIRES all strategies to have identical', + caplog) Backtesting.cleanup() diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index fe562907a..87075d56d 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -291,18 +291,6 @@ def test_advise_all_indicators(default_conf, testdatadir) -> None: assert len(processed['UNITTEST/BTC']) == 103 -def test_populate_any_indicators(default_conf, testdatadir) -> None: - strategy = StrategyResolver.load_strategy(default_conf) - - timerange = TimeRange.parse_timerange('1510694220-1510700340') - data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, - fill_up_missing=True) - processed = strategy.populate_any_indicators('UNITTEST/BTC', data, '5m') - assert processed == data - assert id(processed) == id(data) - assert len(processed['UNITTEST/BTC']) == 103 - - def test_freqai_not_initialized(default_conf) -> None: strategy = StrategyResolver.load_strategy(default_conf) strategy.ft_bot_start() From af137188f4ef135b58f140426f0507ba632d7f82 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Feb 2023 18:05:20 +0100 Subject: [PATCH 039/186] Update wrong FAQ entry --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index bcceaf898..726346f66 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,7 +2,7 @@ ## Supported Markets -Freqtrade supports spot trading only. +Freqtrade supports spot trading, as well as (isolated) futures trading for some selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an uptodate list of supported exchanges. ### Can my bot open short positions? From f4bd424226031d618ca1c9a3d0be6d0a9deae9f2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Feb 2023 18:29:00 +0100 Subject: [PATCH 040/186] Remove deprecated ubuntu image Follows anouncement in https://github.blog/changelog/2022-08-09-github-actions-the-ubuntu-18-04-actions-runner-image-is-being-deprecated-and-will-be-removed-by-12-1-22/ --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63a0b7468..cfc8ac3b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-18.04, ubuntu-20.04, ubuntu-22.04 ] + os: [ ubuntu-20.04, ubuntu-22.04 ] python-version: ["3.8", "3.9", "3.10"] steps: From a4a3d27ac6b4727fe2d5b97540010316ac9eaff1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Feb 2023 19:52:22 +0100 Subject: [PATCH 041/186] Improve FAQ page --- docs/faq.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 726346f66..6ba9d0700 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -253,3 +253,22 @@ You can find further info on expectancy, win rate, risk management and position - https://www.learningmarkets.com/determining-expectancy-in-your-trading/ - http://www.lonestocktrader.com/make-money-trading-positive-expectancy/ - https://www.babypips.com/trading/trade-expectancy-matter + +## Official channels + +Freqtrade is using exclusively the following official channels: + +* [Freqtrade discord server](https://discord.gg/p7nuUNVfP7) +* [Freqtrade documentation (https://freqtrade.io)](https://freqtrade.io) +* [Freqtrade github organization](https://github.com/freqtrade) + +Nobody affiliated with the freqtrade project will ask you about your exchange keys or anything else exposing your funds to exploitation. +Should you be asked to expose your exchange keys or send funds to some random wallet, then please don't follow these instructions. + +Failing to follow these guidelines will not be responsibility of freqtrade. + +## "Freqtrade token" + +Freqtrade does not have a Crypto token offering. + +Token offerings you find on the internet referring Freqtrade, FreqAI or freqUI must be considered to be a scam, trying to exploit freqtrade's popularity for their own, nefarious gains. From 43962476aa033de66696d6e7344f05cfdd02efd9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Feb 2023 19:53:09 +0100 Subject: [PATCH 042/186] Remove non-working links, update links to https --- docs/faq.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 6ba9d0700..b52a77c6b 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -248,10 +248,9 @@ The Edge module is mostly a result of brainstorming of [@mishaker](https://githu You can find further info on expectancy, win rate, risk management and position size in the following sources: - https://www.tradeciety.com/ultimate-math-guide-for-traders/ -- http://www.vantharp.com/tharp-concepts/expectancy.asp - https://samuraitradingacademy.com/trading-expectancy/ - https://www.learningmarkets.com/determining-expectancy-in-your-trading/ -- http://www.lonestocktrader.com/make-money-trading-positive-expectancy/ +- https://www.lonestocktrader.com/make-money-trading-positive-expectancy/ - https://www.babypips.com/trading/trade-expectancy-matter ## Official channels From 48ecc7f6dc6a176ca744d99518388dab5d2086f5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Feb 2023 19:55:32 +0100 Subject: [PATCH 043/186] Update freqai-reinforcement-learning docs closes #8199 --- docs/freqai-reinforcement-learning.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/freqai-reinforcement-learning.md b/docs/freqai-reinforcement-learning.md index 7358f54c3..3810aec4e 100644 --- a/docs/freqai-reinforcement-learning.md +++ b/docs/freqai-reinforcement-learning.md @@ -176,18 +176,19 @@ As you begin to modify the strategy and the prediction model, you will quickly r factor = 100 - # you can use feature values from dataframe - rsi_now = self.raw_features[f"%-rsi-period-10_shift-1_{self.pair}_" - f"{self.config['timeframe']}"].iloc[self._current_tick] + # you can use feature values from dataframe + # Assumes the shifted RSI indicator has been generated in the strategy. + rsi_now = self.raw_features[f"%-rsi-period-10_shift-1_{self.pair}_" + f"{self.config['timeframe']}"].iloc[self._current_tick] - # reward agent for entering trades - if (action in (Actions.Long_enter.value, Actions.Short_enter.value) - and self._position == Positions.Neutral): - if rsi_now < 40: - factor = 40 / rsi_now - else: - factor = 1 - return 25 * factor + # reward agent for entering trades + if (action in (Actions.Long_enter.value, Actions.Short_enter.value) + and self._position == Positions.Neutral): + if rsi_now < 40: + factor = 40 / rsi_now + else: + factor = 1 + return 25 * factor # discourage agent from not entering trades if action == Actions.Neutral.value and self._position == Positions.Neutral: From 62e120a602a5316ccd09adaf3acd3eef95f68b67 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Feb 2023 20:34:55 +0100 Subject: [PATCH 044/186] Remove special treatment of cryptography for raspberries --- requirements.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8b2b99adc..855aa664d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,9 +3,7 @@ pandas==1.5.3 pandas-ta==0.3.14b ccxt==2.8.17 -# Pin cryptography for now due to rust build errors with piwheels -cryptography==38.0.1; platform_machine == 'armv7l' -cryptography==39.0.1; platform_machine != 'armv7l' +cryptography==39.0.1 aiohttp==3.8.4 SQLAlchemy==1.4.46 python-telegram-bot==13.15 From 2b5c11c7b4fd51677dbd311590e88ff0e91aed66 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Tue, 21 Feb 2023 21:08:34 +0100 Subject: [PATCH 045/186] allow users to buffer train data with buffer_train_data_candles parameter --- docs/freqai-parameter-table.md | 1 + freqtrade/constants.py | 3 ++- freqtrade/freqai/data_kitchen.py | 22 ++++++++++++++++++++++ freqtrade/freqai/freqai_interface.py | 4 ++++ tests/freqai/conftest.py | 3 ++- tests/freqai/test_freqai_interface.py | 23 ++++++++++++----------- 6 files changed, 43 insertions(+), 13 deletions(-) diff --git a/docs/freqai-parameter-table.md b/docs/freqai-parameter-table.md index 23d2be8ef..6c3f49d51 100644 --- a/docs/freqai-parameter-table.md +++ b/docs/freqai-parameter-table.md @@ -46,6 +46,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | `outlier_protection_percentage` | Enable to prevent outlier detection methods from discarding too much data. If more than `outlier_protection_percentage` % of points are detected as outliers by the SVM or DBSCAN, FreqAI will log a warning message and ignore outlier detection, i.e., the original dataset will be kept intact. If the outlier protection is triggered, no predictions will be made based on the training dataset.
**Datatype:** Float.
Default: `30`. | `reverse_train_test_order` | Split the feature dataset (see below) and use the latest data split for training and test on historical split of the data. This allows the model to be trained up to the most recent data point, while avoiding overfitting. However, you should be careful to understand the unorthodox nature of this parameter before employing it.
**Datatype:** Boolean.
Default: `False` (no reversal). | `shuffle_after_split` | Split the data into train and test sets, and then shuffle both sets individually.
**Datatype:** Boolean.
Default: `False`. +| `buffer_train_data_candles` | Cut `buffer_train_data_candles` off the beginning and end of the training data *after* the indicators were populated. The main example use is when predicting maxima and minima, the argrelextrema function cannot know the maxima/minima at the edges of the timerange. To improve model accuracy, it is best to compute argrelextrema on the full timerange and then use this function to cut off the edges (buffer) by the kernel. In another case, if the targets are set to a shifted price movement, this buffer is unnecessary because the shifted candles at the end of the timerange will be NaN and FreqAI will automatically cut those off of the training dataset.
**Datatype:** Boolean.
Default: `False`. ### Data split parameters diff --git a/freqtrade/constants.py b/freqtrade/constants.py index a724664a4..c255441f6 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -569,7 +569,8 @@ CONF_SCHEMA = { "nu": {"type": "number", "default": 0.1} }, }, - "shuffle_after_split": {"type": "boolean", "default": False} + "shuffle_after_split": {"type": "boolean", "default": False}, + "buffer_train_data_candles": {"type": "integer", "default": 0} }, "required": ["include_timeframes", "include_corr_pairlist", ] }, diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 30d2509b5..5d8e895a5 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -1562,3 +1562,25 @@ class FreqaiDataKitchen: dataframe.columns = dataframe.columns.str.replace(c, "") return dataframe + + def buffer_timerange(self, timerange: TimeRange): + """ + Buffer the start and end of the timerange. This is used *after* the indicators + are populated. + + The main example use is when predicting maxima and minima, the argrelextrema + function cannot know the maxima/minima at the edges of the timerange. To improve + model accuracy, it is best to compute argrelextrema on the full timerange + and then use this function to cut off the edges (buffer) by the kernel. + + In another case, if the targets are set to a shifted price movement, this + buffer is unnecessary because the shifted candles at the end of the timerange + will be NaN and FreqAI will automatically cut those off of the training + dataset. + """ + buffer = self.freqai_config["feature_parameters"]["buffer_train_data_candles"] + if buffer: + timerange.stopts -= buffer * timeframe_to_seconds(self.config["timeframe"]) + timerange.startts += buffer * timeframe_to_seconds(self.config["timeframe"]) + + return timerange diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index c265e42f9..8630d32fa 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -330,6 +330,8 @@ class IFreqaiModel(ABC): dataframe_base_backtest = strategy.set_freqai_targets( dataframe_base_backtest, metadata=metadata) + tr_train = dk.buffer_timerange(tr_train) + dataframe_train = dk.slice_dataframe(tr_train, dataframe_base_train) dataframe_backtest = dk.slice_dataframe(tr_backtest, dataframe_base_backtest) @@ -614,6 +616,8 @@ class IFreqaiModel(ABC): strategy, corr_dataframes, base_dataframes, pair ) + new_trained_timerange = dk.buffer_timerange(new_trained_timerange) + unfiltered_dataframe = dk.slice_dataframe(new_trained_timerange, unfiltered_dataframe) # find the features indicated by strategy and store in datakitchen diff --git a/tests/freqai/conftest.py b/tests/freqai/conftest.py index 5e8945239..411a6b280 100644 --- a/tests/freqai/conftest.py +++ b/tests/freqai/conftest.py @@ -46,7 +46,8 @@ def freqai_conf(default_conf, tmpdir): "use_SVM_to_remove_outliers": True, "stratify_training_data": 0, "indicator_periods_candles": [10], - "shuffle_after_split": False + "shuffle_after_split": False, + "buffer_train_data_candles": 0 }, "data_split_parameters": {"test_size": 0.33, "shuffle": False}, "model_training_parameters": {"n_estimators": 100}, diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index 55f9e8fde..5565bbed2 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -27,19 +27,19 @@ def is_mac() -> bool: return "Darwin" in machine -@pytest.mark.parametrize('model, pca, dbscan, float32, can_short, shuffle', [ - ('LightGBMRegressor', True, False, True, True, False), - ('XGBoostRegressor', False, True, False, True, False), - ('XGBoostRFRegressor', False, False, False, True, False), - ('CatboostRegressor', False, False, False, True, True), - ('ReinforcementLearner', False, True, False, True, False), - ('ReinforcementLearner_multiproc', False, False, False, True, False), - ('ReinforcementLearner_test_3ac', False, False, False, False, False), - ('ReinforcementLearner_test_3ac', False, False, False, True, False), - ('ReinforcementLearner_test_4ac', False, False, False, True, False) +@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), + ('ReinforcementLearner', False, True, False, True, False, 0), + ('ReinforcementLearner_multiproc', False, False, False, True, False, 0), + ('ReinforcementLearner_test_3ac', False, False, False, False, False, 0), + ('ReinforcementLearner_test_3ac', False, False, False, True, False, 0), + ('ReinforcementLearner_test_4ac', False, False, False, True, False, 0) ]) def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, - dbscan, float32, can_short, shuffle): + dbscan, float32, can_short, shuffle, buffer): if is_arm() and model == 'CatboostRegressor': pytest.skip("CatBoost is not supported on ARM") @@ -55,6 +55,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, freqai_conf['freqai']['feature_parameters'].update({"use_DBSCAN_to_remove_outliers": dbscan}) freqai_conf.update({"reduce_df_footprint": float32}) freqai_conf['freqai']['feature_parameters'].update({"shuffle_after_split": shuffle}) + freqai_conf['freqai']['feature_parameters'].update({"buffer_train_data_candles": buffer}) if 'ReinforcementLearner' in model: model_save_ext = 'zip' From 986bc63e54f9c6953a6bb2064e0fa892eff01339 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Tue, 21 Feb 2023 21:23:58 +0100 Subject: [PATCH 046/186] raise OperationalException if latest historical data candle is older than earliest dataprovider candle --- freqtrade/freqai/data_drawer.py | 34 +++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index 1317ba6d3..883b9d94b 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -627,12 +627,12 @@ class FreqaiDataDrawer: for pair in dk.all_pairs: for tf in feat_params.get("include_timeframes"): - + hist_df = history_data[pair][tf] # check if newest candle is already appended df_dp = strategy.dp.get_pair_dataframe(pair, tf) if len(df_dp.index) == 0: continue - if str(history_data[pair][tf].iloc[-1]["date"]) == str( + if str(hist_df.iloc[-1]["date"]) == str( df_dp.iloc[-1:]["date"].iloc[-1] ): continue @@ -640,28 +640,30 @@ class FreqaiDataDrawer: try: index = ( df_dp.loc[ - df_dp["date"] == history_data[pair][tf].iloc[-1]["date"] + df_dp["date"] == hist_df.iloc[-1]["date"] ].index[0] + 1 ) except IndexError: - if history_data[pair][tf].iloc[-1]['date'] < df_dp['date'].iloc[0]: - index = 0 + if hist_df.iloc[-1]['date'] < df_dp['date'].iloc[0]: + raise OperationalException("In memory historical data is older than " + f"oldest DataProvider candle for {pair} on " + f"timeframe {tf}") else: index = -1 - logger.warning( - f"No common dates in historical data and dataprovider for {pair}. " - f"Appending dataprovider to historical data (full? {not bool(index)})" - "but please be aware that there is likely a gap in the historical " - "data.\n" - f"Historical data ends at {history_data[pair][tf].iloc[-1]['date']} " - f"while dataprovider starts at {df_dp['date'].iloc[0]} and" - f"ends at {df_dp['date'].iloc[0]}." - ) + logger.warning( + f"No common dates in historical data and dataprovider for {pair}. " + f"Appending latest dataprovider candle to historical data " + "but please be aware that there is likely a gap in the historical " + "data. \n" + f"Historical data ends at {hist_df.iloc[-1]['date']} " + f"while dataprovider starts at {df_dp['date'].iloc[0]} and" + f"ends at {df_dp['date'].iloc[0]}." + ) - history_data[pair][tf] = pd.concat( + hist_df = pd.concat( [ - history_data[pair][tf], + hist_df, df_dp.iloc[index:], ], ignore_index=True, From 070a7efd7352ed67440bdbb2325d6faaaccab42b Mon Sep 17 00:00:00 2001 From: Alexander Malysh Date: Wed, 22 Feb 2023 14:52:20 +0100 Subject: [PATCH 047/186] * fixed filename in model_exists --- freqtrade/freqai/freqai_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index c265e42f9..f3b4b0e54 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -569,7 +569,7 @@ class IFreqaiModel(ABC): file_type = ".h5" elif 'stable_baselines' in self.dd.model_type or 'sb3_contrib' == self.dd.model_type: file_type = ".zip" - path_to_modelfile = Path(dk.data_path / f"{dk.model_filename}_model.{file_type}") + path_to_modelfile = Path(dk.data_path / f"{dk.model_filename}_model{file_type}") file_exists = path_to_modelfile.is_file() if file_exists: logger.info("Found model at %s", dk.data_path / dk.model_filename) From 3fbbc57a378a3970cfe8a9804a39bf49e8de9bef Mon Sep 17 00:00:00 2001 From: robcaulk Date: Wed, 22 Feb 2023 17:08:30 +0100 Subject: [PATCH 048/186] add imposter disclaimer to FreqAI front page --- docs/freqai.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/freqai.md b/docs/freqai.md index d84ec8d2b..baca683f8 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -6,6 +6,9 @@ FreqAI is a software designed to automate a variety of tasks associated with training a predictive machine learning model to generate market forecasts given a set of input signals. In general, FreqAI aims to be a sandbox for easily deploying robust machine learning libraries on real-time data ([details](#freqai-position-in-open-source-machine-learning-landscape)). +!!! Danger "DISCLAIMER" + FreqAI is, and always will be, a not-for-profit, open-source project. FreqAI does *not* have a crypto token, FreqAI does *not* sell signals, and FreqAI does not have a domain besides the present [freqtrade documentation](https://www.freqtrade.io/en/latest/freqai/. Please beware of imposter projects, and help us by reporting them to the official Freqtrade discord server. + Features include: * **Self-adaptive retraining** - Retrain models during [live deployments](freqai-running.md#live-deployments) to self-adapt to the market in a supervised manner From ff1258fd209a1812476668d7ac06e18aa0114d00 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 Feb 2023 07:06:59 +0100 Subject: [PATCH 049/186] Better handle random UI backtest errors --- freqtrade/rpc/api_server/api_backtest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index bc2a40d91..d5c6ca2d7 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -10,7 +10,7 @@ from fastapi.exceptions import HTTPException from freqtrade.configuration.config_validation import validate_config_consistency from freqtrade.data.btanalysis import get_backtest_resultlist, load_and_merge_backtest_result from freqtrade.enums import BacktestState -from freqtrade.exceptions import DependencyException +from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.misc import deep_merge_dicts from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestRequest, BacktestResponse) @@ -117,8 +117,8 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac logger.info("Backtest finished.") - except DependencyException as e: - logger.info(f"Backtesting caused an error: {e}") + except (OperationalException, DependencyException) as e: + logger.exception(f"Backtesting caused an error: {e}") pass finally: ApiServer._bgtask_running = False From 18bbfa10e5d33f1255e23cd8477d1345daa6fa2b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 Feb 2023 07:15:50 +0100 Subject: [PATCH 050/186] Reduce amount of variables for API backtesting --- freqtrade/rpc/api_server/api_backtest.py | 68 ++++++++++++------------ freqtrade/rpc/api_server/webserver.py | 10 ++-- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index d5c6ca2d7..f1e2b6cf1 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -60,30 +60,31 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac asyncio.set_event_loop(asyncio.new_event_loop()) try: # Reload strategy - lastconfig = ApiServer._bt_last_config + lastconfig = ApiServer._bt['last_config'] strat = StrategyResolver.load_strategy(btconfig) validate_config_consistency(btconfig) if ( - not ApiServer._bt + not ApiServer._bt['bt'] or lastconfig.get('timeframe') != strat.timeframe or lastconfig.get('timeframe_detail') != btconfig.get('timeframe_detail') or lastconfig.get('timerange') != btconfig['timerange'] ): from freqtrade.optimize.backtesting import Backtesting - ApiServer._bt = Backtesting(btconfig) - ApiServer._bt.load_bt_data_detail() + ApiServer._bt['bt'] = Backtesting(btconfig) + ApiServer._bt['bt'].load_bt_data_detail() else: - ApiServer._bt.config = btconfig - ApiServer._bt.init_backtest() + ApiServer._bt['bt'].config = btconfig + ApiServer._bt['bt'].init_backtest() # Only reload data if timeframe changed. if ( - not ApiServer._bt_data - or not ApiServer._bt_timerange + not ApiServer._bt['data'] + or not ApiServer._bt['timerange'] or lastconfig.get('timeframe') != strat.timeframe or lastconfig.get('timerange') != btconfig['timerange'] ): - ApiServer._bt_data, ApiServer._bt_timerange = ApiServer._bt.load_bt_data() + ApiServer._bt['data'], ApiServer._bt['timerange'] = ApiServer._bt[ + 'bt'].load_bt_data() lastconfig['timerange'] = btconfig['timerange'] lastconfig['timeframe'] = strat.timeframe @@ -91,27 +92,27 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac lastconfig['enable_protections'] = btconfig.get('enable_protections') lastconfig['dry_run_wallet'] = btconfig.get('dry_run_wallet') - ApiServer._bt.enable_protections = btconfig.get('enable_protections', False) - ApiServer._bt.strategylist = [strat] - ApiServer._bt.results = {} - ApiServer._bt.load_prior_backtest() + ApiServer._bt['bt'].enable_protections = btconfig.get('enable_protections', False) + ApiServer._bt['bt'].strategylist = [strat] + ApiServer._bt['bt'].results = {} + ApiServer._bt['bt'].load_prior_backtest() - ApiServer._bt.abort = False - if (ApiServer._bt.results and - strat.get_strategy_name() in ApiServer._bt.results['strategy']): + ApiServer._bt['bt'].abort = False + if (ApiServer._bt['bt'].results and + strat.get_strategy_name() in ApiServer._bt['bt'].results['strategy']): # When previous result hash matches - reuse that result and skip backtesting. logger.info(f'Reusing result of previous backtest for {strat.get_strategy_name()}') else: - min_date, max_date = ApiServer._bt.backtest_one_strategy( - strat, ApiServer._bt_data, ApiServer._bt_timerange) + min_date, max_date = ApiServer._bt['bt'].backtest_one_strategy( + strat, ApiServer._bt['data'], ApiServer._bt['timerange']) - ApiServer._bt.results = generate_backtest_stats( - ApiServer._bt_data, ApiServer._bt.all_results, + ApiServer._bt['bt'].results = generate_backtest_stats( + ApiServer._bt['data'], ApiServer._bt['bt'].all_results, min_date=min_date, max_date=max_date) if btconfig.get('export', 'none') == 'trades': store_backtest_stats( - btconfig['exportfilename'], ApiServer._bt.results, + btconfig['exportfilename'], ApiServer._bt['bt'].results, datetime.now().strftime("%Y-%m-%d_%H-%M-%S") ) @@ -146,13 +147,13 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)): return { "status": "running", "running": True, - "step": ApiServer._bt.progress.action if ApiServer._bt else str(BacktestState.STARTUP), - "progress": ApiServer._bt.progress.progress if ApiServer._bt else 0, + "step": ApiServer._bt['bt'].progress.action if ApiServer._bt['bt'] else str(BacktestState.STARTUP), + "progress": ApiServer._bt['bt'].progress.progress if ApiServer._bt['bt'] else 0, "trade_count": len(LocalTrade.trades), "status_msg": "Backtest running", } - if not ApiServer._bt: + if not ApiServer._bt['bt']: return { "status": "not_started", "running": False, @@ -167,7 +168,7 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)): "status_msg": "Backtest ended", "step": "finished", "progress": 1, - "backtest_result": ApiServer._bt.results, + "backtest_result": ApiServer._bt['bt'].results, } @@ -182,12 +183,12 @@ def api_delete_backtest(ws_mode=Depends(is_webserver_mode)): "progress": 0, "status_msg": "Backtest running", } - if ApiServer._bt: - ApiServer._bt.cleanup() - del ApiServer._bt - ApiServer._bt = None - del ApiServer._bt_data - ApiServer._bt_data = None + if ApiServer._bt['bt']: + ApiServer._bt['bt'].cleanup() + del ApiServer._bt['bt'] + ApiServer._bt['bt'] = None + del ApiServer._bt['data'] + ApiServer._bt['data'] = None logger.info("Backtesting reset") return { "status": "reset", @@ -208,7 +209,7 @@ def api_backtest_abort(ws_mode=Depends(is_webserver_mode)): "progress": 0, "status_msg": "Backtest ended", } - ApiServer._bt.abort = True + ApiServer._bt['bt'].abort = True return { "status": "stopping", "running": False, @@ -225,7 +226,8 @@ def api_backtest_history(config=Depends(get_config), ws_mode=Depends(is_webserve @router.get('/backtest/history/result', response_model=BacktestResponse, tags=['webserver', 'backtest']) -def api_backtest_history_result(filename: str, strategy: str, config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): +def api_backtest_history_result(filename: str, strategy: str, config=Depends(get_config), + ws_mode=Depends(is_webserver_mode)): # Get backtest result history, read from metadata files fn = config['user_data_dir'] / 'backtest_results' / filename results: Dict[str, Any] = { diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index 92bded1c5..b3ef794d8 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -36,10 +36,12 @@ class ApiServer(RPCHandler): _rpc: RPC # Backtesting type: Backtesting - _bt = None - _bt_data = None - _bt_timerange = None - _bt_last_config: Config = {} + _bt: Dict[str, Any] = { + 'bt': None, + 'data': None, + 'timerange': None, + 'last_config': {}, + } _has_rpc: bool = False _bgtask_running: bool = False _config: Config = {} From f8fa5bd96960cc2d91f1b9b419fe6ecb2306a9a5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 Feb 2023 07:17:17 +0100 Subject: [PATCH 051/186] Fix gone wrong noqa ... --- freqtrade/rpc/api_server/api_backtest.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index f1e2b6cf1..ce71467ca 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -26,9 +26,9 @@ router = APIRouter() @router.post('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) -# flake8: noqa: C901 -async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: BackgroundTasks, - config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): +async def api_start_backtest( # noqa: C901 + bt_settings: BacktestRequest, background_tasks: BackgroundTasks, + config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): """Start backtesting if not done so already""" if ApiServer._bgtask_running: raise RPCException('Bot Background task already running') @@ -147,7 +147,8 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)): return { "status": "running", "running": True, - "step": ApiServer._bt['bt'].progress.action if ApiServer._bt['bt'] else str(BacktestState.STARTUP), + "step": (ApiServer._bt['bt'].progress.action if ApiServer._bt['bt'] + else str(BacktestState.STARTUP)), "progress": ApiServer._bt['bt'].progress.progress if ApiServer._bt['bt'] else 0, "trade_count": len(LocalTrade.trades), "status_msg": "Backtest running", @@ -219,13 +220,15 @@ def api_backtest_abort(ws_mode=Depends(is_webserver_mode)): } -@router.get('/backtest/history', response_model=List[BacktestHistoryEntry], tags=['webserver', 'backtest']) +@router.get('/backtest/history', response_model=List[BacktestHistoryEntry], + tags=['webserver', 'backtest']) def api_backtest_history(config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): # Get backtest result history, read from metadata files return get_backtest_resultlist(config['user_data_dir'] / 'backtest_results') -@router.get('/backtest/history/result', response_model=BacktestResponse, tags=['webserver', 'backtest']) +@router.get('/backtest/history/result', response_model=BacktestResponse, + tags=['webserver', 'backtest']) def api_backtest_history_result(filename: str, strategy: str, config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): # Get backtest result history, read from metadata files From 01d51aa979d0755cb9878bbd1db14a004a3dbb8e Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 Feb 2023 07:20:10 +0100 Subject: [PATCH 052/186] Add necesary noqa statements --- freqtrade/templates/FreqaiExampleHybridStrategy.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/freqtrade/templates/FreqaiExampleHybridStrategy.py b/freqtrade/templates/FreqaiExampleHybridStrategy.py index ee574fda3..8a99dabc1 100644 --- a/freqtrade/templates/FreqaiExampleHybridStrategy.py +++ b/freqtrade/templates/FreqaiExampleHybridStrategy.py @@ -1,13 +1,13 @@ import logging from typing import Dict -import numpy as np -import pandas as pd +import numpy as np # noqa +import pandas as pd # noqa import talib.abstract as ta from pandas import DataFrame from technical import qtpylib -from freqtrade.strategy import IntParameter, IStrategy, merge_informative_pair +from freqtrade.strategy import IntParameter, IStrategy, merge_informative_pair # noqa logger = logging.getLogger(__name__) @@ -224,12 +224,11 @@ class FreqaiExampleHybridStrategy(IStrategy): usage example: dataframe["&-target"] = dataframe["close"].shift(-1) / dataframe["close"] """ dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-50) > - dataframe["close"], 'up', 'down') + dataframe["close"], 'up', 'down') return dataframe - # flake8: noqa: C901 - def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # noqa: C901 # User creates their own custom strat here. Present example is a supertrend # based strategy. From 0f878daa9862b777db7e5135c59b39d426382400 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 Feb 2023 19:56:27 +0100 Subject: [PATCH 053/186] Remove some too generic noqa statements --- freqtrade/leverage/__init__.py | 3 +-- freqtrade/mixins/__init__.py | 3 +-- freqtrade/optimize/space/__init__.py | 5 ++--- freqtrade/rpc/__init__.py | 5 ++--- freqtrade/rpc/api_server/__init__.py | 3 +-- freqtrade/rpc/api_server/ws/__init__.py | 11 +++++------ freqtrade/util/__init__.py | 5 ++--- 7 files changed, 14 insertions(+), 21 deletions(-) diff --git a/freqtrade/leverage/__init__.py b/freqtrade/leverage/__init__.py index ae78f4722..d4526dbec 100644 --- a/freqtrade/leverage/__init__.py +++ b/freqtrade/leverage/__init__.py @@ -1,2 +1 @@ -# flake8: noqa: F401 -from freqtrade.leverage.interest import interest +from freqtrade.leverage.interest import interest # noqa: F401 diff --git a/freqtrade/mixins/__init__.py b/freqtrade/mixins/__init__.py index f4a640fa3..c5363c076 100644 --- a/freqtrade/mixins/__init__.py +++ b/freqtrade/mixins/__init__.py @@ -1,2 +1 @@ -# flake8: noqa: F401 -from freqtrade.mixins.logging_mixin import LoggingMixin +from freqtrade.mixins.logging_mixin import LoggingMixin # noqa: F401 diff --git a/freqtrade/optimize/space/__init__.py b/freqtrade/optimize/space/__init__.py index bbdac4ab9..6c59a4d8f 100644 --- a/freqtrade/optimize/space/__init__.py +++ b/freqtrade/optimize/space/__init__.py @@ -1,4 +1,3 @@ -# flake8: noqa: F401 -from skopt.space import Categorical, Dimension, Integer, Real +from skopt.space import Categorical, Dimension, Integer, Real # noqa: F401 -from .decimalspace import SKDecimal +from .decimalspace import SKDecimal # noqa: F401 diff --git a/freqtrade/rpc/__init__.py b/freqtrade/rpc/__init__.py index 957565e2c..07f83abc0 100644 --- a/freqtrade/rpc/__init__.py +++ b/freqtrade/rpc/__init__.py @@ -1,3 +1,2 @@ -# flake8: noqa: F401 -from .rpc import RPC, RPCException, RPCHandler -from .rpc_manager import RPCManager +from .rpc import RPC, RPCException, RPCHandler # noqa: F401 +from .rpc_manager import RPCManager # noqa: F401 diff --git a/freqtrade/rpc/api_server/__init__.py b/freqtrade/rpc/api_server/__init__.py index df255c186..b2ed3e6e0 100644 --- a/freqtrade/rpc/api_server/__init__.py +++ b/freqtrade/rpc/api_server/__init__.py @@ -1,2 +1 @@ -# flake8: noqa: F401 -from .webserver import ApiServer +from .webserver import ApiServer # noqa: F401 diff --git a/freqtrade/rpc/api_server/ws/__init__.py b/freqtrade/rpc/api_server/ws/__init__.py index 0b94d3fee..b76428119 100644 --- a/freqtrade/rpc/api_server/ws/__init__.py +++ b/freqtrade/rpc/api_server/ws/__init__.py @@ -1,7 +1,6 @@ -# flake8: noqa: F401 # isort: off -from freqtrade.rpc.api_server.ws.types import WebSocketType -from freqtrade.rpc.api_server.ws.proxy import WebSocketProxy -from freqtrade.rpc.api_server.ws.serializer import HybridJSONWebSocketSerializer -from freqtrade.rpc.api_server.ws.channel import WebSocketChannel -from freqtrade.rpc.api_server.ws.message_stream import MessageStream +from freqtrade.rpc.api_server.ws.types import WebSocketType # noqa: F401 +from freqtrade.rpc.api_server.ws.proxy import WebSocketProxy # noqa: F401 +from freqtrade.rpc.api_server.ws.serializer import HybridJSONWebSocketSerializer # noqa: F401 +from freqtrade.rpc.api_server.ws.channel import WebSocketChannel # noqa: F401 +from freqtrade.rpc.api_server.ws.message_stream import MessageStream # noqa: F401 diff --git a/freqtrade/util/__init__.py b/freqtrade/util/__init__.py index 7980b7ca2..3c3c034c1 100644 --- a/freqtrade/util/__init__.py +++ b/freqtrade/util/__init__.py @@ -1,3 +1,2 @@ -# flake8: noqa: F401 -from freqtrade.util.ft_precise import FtPrecise -from freqtrade.util.periodic_cache import PeriodicCache +from freqtrade.util.ft_precise import FtPrecise # noqa: F401 +from freqtrade.util.periodic_cache import PeriodicCache # noqa: F401 From 75bc5809a9b18bf1c1b97ce922046fe764be62f6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 Feb 2023 20:02:51 +0100 Subject: [PATCH 054/186] Better handle backtest errors --- freqtrade/rpc/api_server/api_backtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index ce71467ca..77de33994 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -118,7 +118,7 @@ async def api_start_backtest( # noqa: C901 logger.info("Backtest finished.") - except (OperationalException, DependencyException) as e: + except (Exception, OperationalException, DependencyException) as e: logger.exception(f"Backtesting caused an error: {e}") pass finally: From e6766b9b82805d0673155b7da7fc8eca121ec21c Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 Feb 2023 20:22:59 +0100 Subject: [PATCH 055/186] Add bt-error to UI backtest method. --- freqtrade/rpc/api_server/api_backtest.py | 10 ++++++++++ freqtrade/rpc/api_server/webserver.py | 1 + tests/rpc/test_rpc_apiserver.py | 10 ++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 77de33994..d9d7a27f1 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -29,6 +29,7 @@ router = APIRouter() async def api_start_backtest( # noqa: C901 bt_settings: BacktestRequest, background_tasks: BackgroundTasks, config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): + ApiServer._bt['bt_error'] = None """Start backtesting if not done so already""" if ApiServer._bgtask_running: raise RPCException('Bot Background task already running') @@ -120,6 +121,7 @@ async def api_start_backtest( # noqa: C901 except (Exception, OperationalException, DependencyException) as e: logger.exception(f"Backtesting caused an error: {e}") + ApiServer._bt['bt_error'] = str(e) pass finally: ApiServer._bgtask_running = False @@ -162,6 +164,14 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)): "progress": 0, "status_msg": "Backtest not yet executed" } + if ApiServer._bt['bt_error']: + return { + "status": "error", + "running": False, + "step": "", + "progress": 0, + "status_msg": f"Backtest failed with {ApiServer._bt['bt_error']}" + } return { "status": "ended", diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index b3ef794d8..b53662451 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -41,6 +41,7 @@ class ApiServer(RPCHandler): 'data': None, 'timerange': None, 'last_config': {}, + 'bt_error': None, } _has_rpc: bool = False _bgtask_running: bool = False diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 94b210c76..43d9abb78 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1737,9 +1737,15 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): data['stake_amount'] = 101 mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy', - side_effect=DependencyException()) + side_effect=DependencyException('DeadBeef')) rc = client_post(client, f"{BASE_URI}/backtest", data=data) - assert log_has("Backtesting caused an error: ", caplog) + 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'] # Delete backtesting to avoid leakage since the backtest-object may stick around. rc = client_delete(client, f"{BASE_URI}/backtest") From 2bc9413be11288d0b42c0c354cb2d988eca5af22 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 Feb 2023 20:58:24 +0100 Subject: [PATCH 056/186] Fix minor stylistic errors --- freqtrade/__main__.py | 0 freqtrade/commands/analyze_commands.py | 0 freqtrade/commands/hyperopt_commands.py | 0 freqtrade/data/entryexitanalysis.py | 0 freqtrade/optimize/hyperopt_tools.py | 0 freqtrade/vendor/qtpylib/indicators.py | 1 - freqtrade/worker.py | 0 scripts/ws_client.py | 0 tests/data/test_entryexitanalysis.py | 0 tests/exchange/test_ccxt_compat.py | 2 +- tests/strategy/test_interface.py | 4 ++-- 11 files changed, 3 insertions(+), 4 deletions(-) mode change 100644 => 100755 freqtrade/__main__.py mode change 100755 => 100644 freqtrade/commands/analyze_commands.py mode change 100755 => 100644 freqtrade/commands/hyperopt_commands.py mode change 100755 => 100644 freqtrade/data/entryexitanalysis.py mode change 100755 => 100644 freqtrade/optimize/hyperopt_tools.py mode change 100755 => 100644 freqtrade/worker.py mode change 100644 => 100755 scripts/ws_client.py mode change 100755 => 100644 tests/data/test_entryexitanalysis.py diff --git a/freqtrade/__main__.py b/freqtrade/__main__.py old mode 100644 new mode 100755 diff --git a/freqtrade/commands/analyze_commands.py b/freqtrade/commands/analyze_commands.py old mode 100755 new mode 100644 diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py old mode 100755 new mode 100644 diff --git a/freqtrade/data/entryexitanalysis.py b/freqtrade/data/entryexitanalysis.py old mode 100755 new mode 100644 diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py old mode 100755 new mode 100644 diff --git a/freqtrade/vendor/qtpylib/indicators.py b/freqtrade/vendor/qtpylib/indicators.py index 4f14ae13c..3da4f038d 100644 --- a/freqtrade/vendor/qtpylib/indicators.py +++ b/freqtrade/vendor/qtpylib/indicators.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # # QTPyLib: Quantitative Trading Python Library diff --git a/freqtrade/worker.py b/freqtrade/worker.py old mode 100755 new mode 100644 diff --git a/scripts/ws_client.py b/scripts/ws_client.py old mode 100644 new mode 100755 diff --git a/tests/data/test_entryexitanalysis.py b/tests/data/test_entryexitanalysis.py old mode 100755 new mode 100644 diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index f1d240f9f..bbeb56c6a 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -309,7 +309,7 @@ def exchange(request, exchange_conf): @pytest.fixture(params=EXCHANGES, scope="class") def exchange_futures(request, exchange_conf, class_mocker): - if not EXCHANGES[request.param].get('futures') is True: + if EXCHANGES[request.param].get('futures') is not True: yield None, request.param else: exchange_conf = set_test_proxy( diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index fe562907a..0b30d2059 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -214,12 +214,12 @@ def test_ignore_expired_candle(default_conf): current_time = latest_date + timedelta(seconds=30 + 300) - assert not strategy.ignore_expired_candle( + assert strategy.ignore_expired_candle( latest_date=latest_date, current_time=current_time, timeframe_seconds=300, enter=True - ) is True + ) is not True def test_assert_df_raise(mocker, caplog, ohlcv_history): From 549a0e1c447df9fe75ee834dad58d83ee4cd8bb9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 Feb 2023 21:06:07 +0100 Subject: [PATCH 057/186] Add ruff linting - initial configuration --- .github/workflows/ci.yml | 12 ++++++++++++ .pre-commit-config.yaml | 6 ++++++ pyproject.toml | 14 ++++++++++++++ requirements-dev.txt | 1 + 4 files changed, 33 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cfc8ac3b6..4cff9ef7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,6 +98,10 @@ jobs: run: | isort --check . + - name: Ruff - linting + run: | + ruff check . + - name: Mypy run: | mypy freqtrade scripts tests @@ -194,6 +198,10 @@ jobs: run: | isort --check . + - name: Ruff - linting + run: | + ruff check . + - name: Mypy run: | mypy freqtrade scripts @@ -252,6 +260,10 @@ jobs: run: | flake8 + - name: Ruff - linting + run: | + ruff check . + - name: Mypy run: | mypy freqtrade scripts tests diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 57ce81b8c..58f526ce9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,6 +27,12 @@ repos: name: isort (python) # stages: [push] + - repo: https://github.com/charliermarsh/ruff-pre-commit + # Ruff version. + rev: 'v0.0.251' + hooks: + - id: ruff + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: diff --git a/pyproject.toml b/pyproject.toml index 82d4ceaf8..7da2a2200 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,3 +56,17 @@ exclude = [ "build_helpers/*.py", ] ignore = ["freqtrade/vendor/**"] + + +[tool.ruff] +line-length = 100 +extend-exclude = [".env"] +extend-select = [ + "TID", + "EXE", + "YTT", + # "DTZ", + # "RSE", + # "TCH", + # "PTH", +] diff --git a/requirements-dev.txt b/requirements-dev.txt index 32b7cfcc5..287cb8ae9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,6 +9,7 @@ coveralls==3.3.1 flake8==6.0.0 flake8-tidy-imports==4.8.0 +ruff==0.0.251 mypy==1.0.1 pre-commit==3.0.4 pytest==7.2.1 From b4ea37d59862ef5769468a0749c5f4e4ee4254da Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 Feb 2023 21:08:17 +0100 Subject: [PATCH 058/186] Remove flake8 in favor of ruff --- .github/workflows/ci.yml | 12 ------------ CONTRIBUTING.md | 11 ++++++----- docs/developer.md | 2 +- environment.yml | 3 +-- requirements-dev.txt | 2 -- setup.py | 2 -- 6 files changed, 8 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cff9ef7a..e00b9040b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,10 +90,6 @@ jobs: freqtrade create-userdir --userdir user_data freqtrade hyperopt --datadir tests/testdata -e 6 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily --print-all - - name: Flake8 - run: | - flake8 - - name: Sort imports (isort) run: | isort --check . @@ -190,10 +186,6 @@ jobs: freqtrade create-userdir --userdir user_data freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily --print-all - - name: Flake8 - run: | - flake8 - - name: Sort imports (isort) run: | isort --check . @@ -256,10 +248,6 @@ jobs: freqtrade create-userdir --userdir user_data freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily --print-all - - name: Flake8 - run: | - flake8 - - name: Ruff - linting run: | ruff check . diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b4e0bc024..040aae39c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,16 +45,17 @@ pytest tests/test_.py::test_ ### 2. Test if your code is PEP8 compliant -#### Run Flake8 +#### Run Ruff ```bash -flake8 freqtrade tests scripts +ruff . ``` -We receive a lot of code that fails the `flake8` checks. +We receive a lot of code that fails the `ruff` checks. To help with that, we encourage you to install the git pre-commit -hook that will warn you when you try to commit code that fails these checks. -Guide for installing them is [here](http://flake8.pycqa.org/en/latest/user/using-hooks.html). +hook that will warn you when you try to commit code that fails these checks. + +you can manually run pre-commit with `pre-commit run -a`. ##### Additional styles applied diff --git a/docs/developer.md b/docs/developer.md index 0546c20e9..1bc75551f 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -24,7 +24,7 @@ This will spin up a local server (usually on port 8000) so you can see if everyt To configure a development environment, you can either use the provided [DevContainer](#devcontainer-setup), or use the `setup.sh` script and answer "y" when asked "Do you want to install dependencies for dev [y/N]? ". Alternatively (e.g. if your system is not supported by the setup.sh script), follow the manual installation process and run `pip3 install -e .[all]`. -This will install all required tools for development, including `pytest`, `flake8`, `mypy`, and `coveralls`. +This will install all required tools for development, including `pytest`, `ruff`, `mypy`, and `coveralls`. Then install the git hook scripts by running `pre-commit install`, so your changes will be verified locally before committing. This avoids a lot of waiting for CI already, as some basic formatting checks are done locally on your machine. diff --git a/environment.yml b/environment.yml index 5b039e7f7..171e7c3da 100644 --- a/environment.yml +++ b/environment.yml @@ -41,7 +41,6 @@ dependencies: # 2/4 req dev - coveralls - - flake8 - mypy - pytest - pytest-asyncio @@ -70,6 +69,6 @@ dependencies: - tables - pytest-random-order - ccxt - - flake8-tidy-imports + - ruff - -e . # - python-rapidjso diff --git a/requirements-dev.txt b/requirements-dev.txt index 287cb8ae9..c23447694 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,8 +7,6 @@ -r docs/requirements-docs.txt coveralls==3.3.1 -flake8==6.0.0 -flake8-tidy-imports==4.8.0 ruff==0.0.251 mypy==1.0.1 pre-commit==3.0.4 diff --git a/setup.py b/setup.py index 30aacc3f2..edd7b243b 100644 --- a/setup.py +++ b/setup.py @@ -32,8 +32,6 @@ hdf5 = [ develop = [ 'coveralls', - 'flake8', - 'flake8-tidy-imports', 'mypy', 'pytest', 'pytest-asyncio', From 9633081c316fb21a716dddb7445ad77b88df4084 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Wed, 22 Feb 2023 22:01:41 +0100 Subject: [PATCH 059/186] remove remnants of follower, clean data-drawer, improve doc --- docs/freqai.md | 2 +- freqtrade/freqai/data_drawer.py | 26 ++------------------------ freqtrade/freqai/freqai_interface.py | 6 +++--- 3 files changed, 6 insertions(+), 28 deletions(-) diff --git a/docs/freqai.md b/docs/freqai.md index d84ec8d2b..b1cde2780 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -19,7 +19,7 @@ Features include: * **Automatic data download** - Compute timeranges for data downloads and update historic data (in live deployments) * **Cleaning of incoming data** - Handle NaNs safely before training and model inferencing * **Dimensionality reduction** - Reduce the size of the training data via [Principal Component Analysis](freqai-feature-engineering.md#data-dimensionality-reduction-with-principal-component-analysis) -* **Deploying bot fleets** - Set one bot to train models while a fleet of [follower bots](freqai-running.md#setting-up-a-follower) inference the models and handle trades +* **Deploying bot fleets** - Set one bot to train models while a fleet of [consumers](producer-consumer.md) use signals. ## Quick start diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index cb5b0f0fb..fc4c9f7b6 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -72,12 +72,7 @@ class FreqaiDataDrawer: self.model_return_values: Dict[str, DataFrame] = {} self.historic_data: Dict[str, Dict[str, DataFrame]] = {} self.historic_predictions: Dict[str, DataFrame] = {} - self.follower_dict: Dict[str, pair_info] = {} self.full_path = full_path - self.follower_name: str = self.config.get("bot_name", "follower1") - self.follower_dict_path = Path( - self.full_path / f"follower_dictionary-{self.follower_name}.json" - ) self.historic_predictions_path = Path(self.full_path / "historic_predictions.pkl") self.historic_predictions_bkp_path = Path( self.full_path / "historic_predictions.backup.pkl") @@ -218,14 +213,6 @@ class FreqaiDataDrawer: rapidjson.dump(self.pair_dict, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE) - def save_follower_dict_to_disk(self): - """ - Save follower dictionary to disk (used by strategy for persistent prediction targets) - """ - with open(self.follower_dict_path, "w") as fp: - rapidjson.dump(self.follower_dict, fp, default=self.np_encoder, - number_mode=rapidjson.NM_NATIVE) - def save_global_metadata_to_disk(self, metadata: Dict[str, Any]): """ Save global metadata json to disk @@ -239,7 +226,7 @@ class FreqaiDataDrawer: if isinstance(object, np.generic): return object.item() - def get_pair_dict_info(self, pair: str) -> Tuple[str, int, bool]: + def get_pair_dict_info(self, pair: str) -> Tuple[str, int]: """ Locate and load existing model metadata from persistent storage. If not located, create a new one and append the current pair to it and prepare it for its first @@ -248,12 +235,9 @@ class FreqaiDataDrawer: :return: model_filename: str = unique filename used for loading persistent objects from disk trained_timestamp: int = the last time the coin was trained - return_null_array: bool = Follower could not find pair metadata """ pair_dict = self.pair_dict.get(pair) - # data_path_set = self.pair_dict.get(pair, self.empty_pair_dict).get("data_path", "") - return_null_array = False if pair_dict: model_filename = pair_dict["model_filename"] @@ -263,7 +247,7 @@ class FreqaiDataDrawer: model_filename = "" trained_timestamp = 0 - return model_filename, trained_timestamp, return_null_array + return model_filename, trained_timestamp def set_pair_dict_info(self, metadata: dict) -> None: pair_in_dict = self.pair_dict.get(metadata["pair"]) @@ -417,12 +401,6 @@ class FreqaiDataDrawer: shutil.rmtree(v) deleted += 1 - def update_follower_metadata(self): - # follower needs to load from disk to get any changes made by leader to pair_dict - self.load_drawer_from_disk() - if self.config.get("freqai", {}).get("purge_old_models", False): - self.purge_old_models() - def save_metadata(self, dk: FreqaiDataKitchen) -> None: """ Saves only metadata for backtesting studies if user prefers diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index f3b4b0e54..fab5cbff8 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -227,7 +227,7 @@ class IFreqaiModel(ABC): logger.warning(f'{pair} not in current whitelist, removing from train queue.') continue - (_, trained_timestamp, _) = self.dd.get_pair_dict_info(pair) + (_, trained_timestamp) = self.dd.get_pair_dict_info(pair) dk = FreqaiDataKitchen(self.config, self.live, pair) ( @@ -285,7 +285,7 @@ class IFreqaiModel(ABC): # following tr_train. Both of these windows slide through the # entire backtest for tr_train, tr_backtest in zip(dk.training_timeranges, dk.backtesting_timeranges): - (_, _, _) = self.dd.get_pair_dict_info(pair) + (_, _) = self.dd.get_pair_dict_info(pair) train_it += 1 total_trains = len(dk.backtesting_timeranges) self.training_timerange = tr_train @@ -382,7 +382,7 @@ class IFreqaiModel(ABC): """ # get the model metadata associated with the current pair - (_, trained_timestamp, return_null_array) = self.dd.get_pair_dict_info(metadata["pair"]) + (_, trained_timestamp) = self.dd.get_pair_dict_info(metadata["pair"]) # append the historic data once per round if self.dd.historic_data: From b8f011a2ab70123f388d0902be887c801baa16f8 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Wed, 22 Feb 2023 22:27:56 +0100 Subject: [PATCH 060/186] give users ability to decide how many models to keep in dry/live --- config_examples/config_freqai.example.json | 2 +- docs/freqai-configuration.md | 2 +- docs/freqai-parameter-table.md | 2 +- freqtrade/constants.py | 2 +- freqtrade/freqai/data_drawer.py | 10 ++++++++-- freqtrade/freqai/freqai_interface.py | 3 +-- freqtrade/templates/FreqaiExampleHybridStrategy.py | 2 +- tests/freqai/conftest.py | 2 +- 8 files changed, 15 insertions(+), 10 deletions(-) diff --git a/config_examples/config_freqai.example.json b/config_examples/config_freqai.example.json index 645c30227..65a93379e 100644 --- a/config_examples/config_freqai.example.json +++ b/config_examples/config_freqai.example.json @@ -48,7 +48,7 @@ ], "freqai": { "enabled": true, - "purge_old_models": true, + "purge_old_models": 2, "train_period_days": 15, "backtest_period_days": 7, "live_retrain_hours": 0, diff --git a/docs/freqai-configuration.md b/docs/freqai-configuration.md index 88415bf59..886dc2338 100644 --- a/docs/freqai-configuration.md +++ b/docs/freqai-configuration.md @@ -9,7 +9,7 @@ FreqAI is configured through the typical [Freqtrade config file](configuration.m ```json "freqai": { "enabled": true, - "purge_old_models": true, + "purge_old_models": 2, "train_period_days": 30, "backtest_period_days": 7, "identifier" : "unique-id", diff --git a/docs/freqai-parameter-table.md b/docs/freqai-parameter-table.md index 23d2be8ef..7f0b0c213 100644 --- a/docs/freqai-parameter-table.md +++ b/docs/freqai-parameter-table.md @@ -15,7 +15,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | `identifier` | **Required.**
A unique ID for the current model. If models are saved to disk, the `identifier` allows for reloading specific pre-trained models/data.
**Datatype:** String. | `live_retrain_hours` | Frequency of retraining during dry/live runs.
**Datatype:** Float > 0.
Default: `0` (models retrain as often as possible). | `expiration_hours` | Avoid making predictions if a model is more than `expiration_hours` old.
**Datatype:** Positive integer.
Default: `0` (models never expire). -| `purge_old_models` | Delete all unused models during live runs (not relevant to backtesting). If set to false (not default), dry/live runs will accumulate all unused models to disk. If
**Datatype:** Boolean.
Default: `True`. +| `purge_old_models` | Number of models to keep on disk (not relevant to backtesting). Default is 2, dry/live runs will keep 2 models on disk. Setting to 0 keeps all models. If
**Datatype:** Boolean.
Default: `2`. | `save_backtest_models` | Save models to disk when running backtesting. Backtesting operates most efficiently by saving the prediction data and reusing them directly for subsequent runs (when you wish to tune entry/exit parameters). Saving backtesting models to disk also allows to use the same model files for starting a dry/live instance with the same model `identifier`.
**Datatype:** Boolean.
Default: `False` (no models are saved). | `fit_live_predictions_candles` | Number of historical candles to use for computing target (label) statistics from prediction data, instead of from the training dataset (more information can be found [here](freqai-configuration.md#creating-a-dynamic-target-threshold)).
**Datatype:** Positive integer. | `continual_learning` | Use the final state of the most recently trained model as starting point for the new model, allowing for incremental learning (more information can be found [here](freqai-running.md#continual-learning)).
**Datatype:** Boolean.
Default: `False`. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index a724664a4..84c9b5cc9 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -546,7 +546,7 @@ CONF_SCHEMA = { "enabled": {"type": "boolean", "default": False}, "keras": {"type": "boolean", "default": False}, "write_metrics_to_disk": {"type": "boolean", "default": False}, - "purge_old_models": {"type": "boolean", "default": True}, + "purge_old_models": {"type": ["boolean", "number"], "default": 2}, "conv_width": {"type": "integer", "default": 1}, "train_period_days": {"type": "integer", "default": 0}, "backtest_period_days": {"type": "number", "default": 7}, diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index fc4c9f7b6..c90bb23fc 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -366,6 +366,12 @@ class FreqaiDataDrawer: def purge_old_models(self) -> None: + num_keep = self.freqai_info["purge_old_models"] + if not num_keep: + return + elif type(num_keep) == bool: + num_keep = 2 + model_folders = [x for x in self.full_path.iterdir() if x.is_dir()] pattern = re.compile(r"sub-train-(\w+)_(\d{10})") @@ -388,11 +394,11 @@ class FreqaiDataDrawer: delete_dict[coin]["timestamps"][int(timestamp)] = dir for coin in delete_dict: - if delete_dict[coin]["num_folders"] > 2: + if delete_dict[coin]["num_folders"] > num_keep: sorted_dict = collections.OrderedDict( sorted(delete_dict[coin]["timestamps"].items()) ) - num_delete = len(sorted_dict) - 2 + num_delete = len(sorted_dict) - num_keep deleted = 0 for k, v in sorted_dict.items(): if deleted >= num_delete: diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index fab5cbff8..0a4648c8a 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -629,8 +629,7 @@ class IFreqaiModel(ABC): if self.plot_features: plot_feature_importance(model, pair, dk, self.plot_features) - if self.freqai_info.get("purge_old_models", False): - self.dd.purge_old_models() + self.dd.purge_old_models() def set_initial_historic_predictions( self, pred_df: DataFrame, dk: FreqaiDataKitchen, pair: str, strat_df: DataFrame diff --git a/freqtrade/templates/FreqaiExampleHybridStrategy.py b/freqtrade/templates/FreqaiExampleHybridStrategy.py index 8a99dabc1..0e7113f8c 100644 --- a/freqtrade/templates/FreqaiExampleHybridStrategy.py +++ b/freqtrade/templates/FreqaiExampleHybridStrategy.py @@ -27,7 +27,7 @@ class FreqaiExampleHybridStrategy(IStrategy): "freqai": { "enabled": true, - "purge_old_models": true, + "purge_old_models": 2, "train_period_days": 15, "identifier": "uniqe-id", "feature_parameters": { diff --git a/tests/freqai/conftest.py b/tests/freqai/conftest.py index 5e8945239..97f2c2246 100644 --- a/tests/freqai/conftest.py +++ b/tests/freqai/conftest.py @@ -27,7 +27,7 @@ def freqai_conf(default_conf, tmpdir): "timerange": "20180110-20180115", "freqai": { "enabled": True, - "purge_old_models": True, + "purge_old_models": 2, "train_period_days": 2, "backtest_period_days": 10, "live_retrain_hours": 0, From 150b7f9c87728dc0a87657340be3e21d6c546b89 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Wed, 22 Feb 2023 22:33:41 +0100 Subject: [PATCH 061/186] lighten the disclaimer message --- docs/freqai.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/freqai.md b/docs/freqai.md index baca683f8..d03f0c0f5 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -6,8 +6,8 @@ FreqAI is a software designed to automate a variety of tasks associated with training a predictive machine learning model to generate market forecasts given a set of input signals. In general, FreqAI aims to be a sandbox for easily deploying robust machine learning libraries on real-time data ([details](#freqai-position-in-open-source-machine-learning-landscape)). -!!! Danger "DISCLAIMER" - FreqAI is, and always will be, a not-for-profit, open-source project. FreqAI does *not* have a crypto token, FreqAI does *not* sell signals, and FreqAI does not have a domain besides the present [freqtrade documentation](https://www.freqtrade.io/en/latest/freqai/. Please beware of imposter projects, and help us by reporting them to the official Freqtrade discord server. +!!! Note: + FreqAI is, and always will be, a not-for-profit, open-source project. FreqAI does *not* have a crypto token, FreqAI does *not* sell signals, and FreqAI does not have a domain besides the present [freqtrade documentation](https://www.freqtrade.io/en/latest/freqai/. Features include: From 23a71680deed34548cd0ccd4c9c6d4781dad56b5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 23 Feb 2023 06:29:58 +0100 Subject: [PATCH 062/186] Update Doc-box typo --- docs/freqai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/freqai.md b/docs/freqai.md index d03f0c0f5..c74360c62 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -6,7 +6,7 @@ FreqAI is a software designed to automate a variety of tasks associated with training a predictive machine learning model to generate market forecasts given a set of input signals. In general, FreqAI aims to be a sandbox for easily deploying robust machine learning libraries on real-time data ([details](#freqai-position-in-open-source-machine-learning-landscape)). -!!! Note: +!!! Note FreqAI is, and always will be, a not-for-profit, open-source project. FreqAI does *not* have a crypto token, FreqAI does *not* sell signals, and FreqAI does not have a domain besides the present [freqtrade documentation](https://www.freqtrade.io/en/latest/freqai/. Features include: From bf968a9fd878cfdb37fe7b0a5865f82767921cf6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 23 Feb 2023 06:51:03 +0100 Subject: [PATCH 063/186] Use actions as documented --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e00b9040b..17c0efd6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,9 +94,9 @@ jobs: run: | isort --check . - - name: Ruff - linting + - name: Run Ruff run: | - ruff check . + ruff check --format=github . - name: Mypy run: | @@ -190,9 +190,9 @@ jobs: run: | isort --check . - - name: Ruff - linting + - name: Run Ruff run: | - ruff check . + ruff check --format=github . - name: Mypy run: | @@ -248,9 +248,9 @@ jobs: freqtrade create-userdir --userdir user_data freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily --print-all - - name: Ruff - linting + - name: Run Ruff run: | - ruff check . + ruff check --format=github . - name: Mypy run: | From 6b829d839b93d5a793a7ff70bfea632ce6a05655 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 23 Feb 2023 07:12:54 +0100 Subject: [PATCH 064/186] Improve ruff config --- pyproject.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7da2a2200..8a7750731 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,11 +62,11 @@ ignore = ["freqtrade/vendor/**"] line-length = 100 extend-exclude = [".env"] extend-select = [ - "TID", - "EXE", - "YTT", - # "DTZ", - # "RSE", - # "TCH", - # "PTH", + "TID", # flake8-tidy-imports + # "EXE", # flake8-executable + "YTT", # flake8-2020 + # "DTZ", # flake8-datetimez + # "RSE", # flake8-raise + # "TCH", # flake8-type-checking + # "PTH", # flake8-use-pathlib ] From 7d906fd4c2d0885cd075865a647b8be7dc92561a Mon Sep 17 00:00:00 2001 From: Joe Schr <8218910+TheJoeSchr@users.noreply.github.com> Date: Wed, 22 Feb 2023 12:26:12 +0100 Subject: [PATCH 065/186] refactor(if-gate): use temp variable instead of if-gate --- freqtrade/data/dataprovider.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index 78390e3a4..3991432a4 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -424,10 +424,8 @@ class DataProvider: """ if self._exchange is None: raise OperationalException(NO_EXCHANGE_EXCEPTION) - if helping_pairs: - self._exchange.refresh_latest_ohlcv(pairlist + helping_pairs) - else: - self._exchange.refresh_latest_ohlcv(pairlist) + final_pairs = (pairlist + helping_pairs) if helping_pairs else pairlist + self._exchange.refresh_latest_ohlcv(final_pairs) @property def available_pairs(self) -> ListPairsWithTimeframes: From 63e5d33028809847f7a16c54d3245f57d166bc66 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 Feb 2023 20:02:51 +0100 Subject: [PATCH 066/186] Better handle backtest errors --- freqtrade/rpc/api_server/api_backtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index ce71467ca..77de33994 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -118,7 +118,7 @@ async def api_start_backtest( # noqa: C901 logger.info("Backtest finished.") - except (OperationalException, DependencyException) as e: + except (Exception, OperationalException, DependencyException) as e: logger.exception(f"Backtesting caused an error: {e}") pass finally: From 659140e190a35aa6fe2d8cb0b9a12997d70c59de Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 Feb 2023 20:22:59 +0100 Subject: [PATCH 067/186] Add bt-error to UI backtest method. --- freqtrade/rpc/api_server/api_backtest.py | 10 ++++++++++ freqtrade/rpc/api_server/webserver.py | 1 + tests/rpc/test_rpc_apiserver.py | 10 ++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 77de33994..d9d7a27f1 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -29,6 +29,7 @@ router = APIRouter() async def api_start_backtest( # noqa: C901 bt_settings: BacktestRequest, background_tasks: BackgroundTasks, config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): + ApiServer._bt['bt_error'] = None """Start backtesting if not done so already""" if ApiServer._bgtask_running: raise RPCException('Bot Background task already running') @@ -120,6 +121,7 @@ async def api_start_backtest( # noqa: C901 except (Exception, OperationalException, DependencyException) as e: logger.exception(f"Backtesting caused an error: {e}") + ApiServer._bt['bt_error'] = str(e) pass finally: ApiServer._bgtask_running = False @@ -162,6 +164,14 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)): "progress": 0, "status_msg": "Backtest not yet executed" } + if ApiServer._bt['bt_error']: + return { + "status": "error", + "running": False, + "step": "", + "progress": 0, + "status_msg": f"Backtest failed with {ApiServer._bt['bt_error']}" + } return { "status": "ended", diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index b3ef794d8..b53662451 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -41,6 +41,7 @@ class ApiServer(RPCHandler): 'data': None, 'timerange': None, 'last_config': {}, + 'bt_error': None, } _has_rpc: bool = False _bgtask_running: bool = False diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 94b210c76..43d9abb78 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1737,9 +1737,15 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): data['stake_amount'] = 101 mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy', - side_effect=DependencyException()) + side_effect=DependencyException('DeadBeef')) rc = client_post(client, f"{BASE_URI}/backtest", data=data) - assert log_has("Backtesting caused an error: ", caplog) + 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'] # Delete backtesting to avoid leakage since the backtest-object may stick around. rc = client_delete(client, f"{BASE_URI}/backtest") From 34c42be74ff3bc2d51ce102a3d928dc1c1cd79a4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 Feb 2023 20:58:24 +0100 Subject: [PATCH 068/186] Fix minor stylistic errors --- freqtrade/__main__.py | 0 freqtrade/commands/analyze_commands.py | 0 freqtrade/commands/hyperopt_commands.py | 0 freqtrade/data/entryexitanalysis.py | 0 freqtrade/optimize/hyperopt_tools.py | 0 freqtrade/vendor/qtpylib/indicators.py | 1 - freqtrade/worker.py | 0 scripts/ws_client.py | 0 tests/data/test_entryexitanalysis.py | 0 tests/exchange/test_ccxt_compat.py | 2 +- tests/strategy/test_interface.py | 4 ++-- 11 files changed, 3 insertions(+), 4 deletions(-) mode change 100644 => 100755 freqtrade/__main__.py mode change 100755 => 100644 freqtrade/commands/analyze_commands.py mode change 100755 => 100644 freqtrade/commands/hyperopt_commands.py mode change 100755 => 100644 freqtrade/data/entryexitanalysis.py mode change 100755 => 100644 freqtrade/optimize/hyperopt_tools.py mode change 100755 => 100644 freqtrade/worker.py mode change 100644 => 100755 scripts/ws_client.py mode change 100755 => 100644 tests/data/test_entryexitanalysis.py diff --git a/freqtrade/__main__.py b/freqtrade/__main__.py old mode 100644 new mode 100755 diff --git a/freqtrade/commands/analyze_commands.py b/freqtrade/commands/analyze_commands.py old mode 100755 new mode 100644 diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py old mode 100755 new mode 100644 diff --git a/freqtrade/data/entryexitanalysis.py b/freqtrade/data/entryexitanalysis.py old mode 100755 new mode 100644 diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py old mode 100755 new mode 100644 diff --git a/freqtrade/vendor/qtpylib/indicators.py b/freqtrade/vendor/qtpylib/indicators.py index 4f14ae13c..3da4f038d 100644 --- a/freqtrade/vendor/qtpylib/indicators.py +++ b/freqtrade/vendor/qtpylib/indicators.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # # QTPyLib: Quantitative Trading Python Library diff --git a/freqtrade/worker.py b/freqtrade/worker.py old mode 100755 new mode 100644 diff --git a/scripts/ws_client.py b/scripts/ws_client.py old mode 100644 new mode 100755 diff --git a/tests/data/test_entryexitanalysis.py b/tests/data/test_entryexitanalysis.py old mode 100755 new mode 100644 diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index f1d240f9f..bbeb56c6a 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -309,7 +309,7 @@ def exchange(request, exchange_conf): @pytest.fixture(params=EXCHANGES, scope="class") def exchange_futures(request, exchange_conf, class_mocker): - if not EXCHANGES[request.param].get('futures') is True: + if EXCHANGES[request.param].get('futures') is not True: yield None, request.param else: exchange_conf = set_test_proxy( diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index fe562907a..0b30d2059 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -214,12 +214,12 @@ def test_ignore_expired_candle(default_conf): current_time = latest_date + timedelta(seconds=30 + 300) - assert not strategy.ignore_expired_candle( + assert strategy.ignore_expired_candle( latest_date=latest_date, current_time=current_time, timeframe_seconds=300, enter=True - ) is True + ) is not True def test_assert_df_raise(mocker, caplog, ohlcv_history): From 5ac4b81a5d646ef755466065c296660c5ff8911e Mon Sep 17 00:00:00 2001 From: robcaulk Date: Fri, 24 Feb 2023 10:50:39 +0100 Subject: [PATCH 069/186] fix link in freqai index.md --- docs/freqai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/freqai.md b/docs/freqai.md index c74360c62..552417623 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -7,7 +7,7 @@ FreqAI is a software designed to automate a variety of tasks associated with training a predictive machine learning model to generate market forecasts given a set of input signals. In general, FreqAI aims to be a sandbox for easily deploying robust machine learning libraries on real-time data ([details](#freqai-position-in-open-source-machine-learning-landscape)). !!! Note - FreqAI is, and always will be, a not-for-profit, open-source project. FreqAI does *not* have a crypto token, FreqAI does *not* sell signals, and FreqAI does not have a domain besides the present [freqtrade documentation](https://www.freqtrade.io/en/latest/freqai/. + FreqAI is, and always will be, a not-for-profit, open-source project. FreqAI does *not* have a crypto token, FreqAI does *not* sell signals, and FreqAI does not have a domain besides the present [freqtrade documentation](https://www.freqtrade.io/en/latest/freqai/). Features include: From c283e223258d5be1e61627e0276445e1cfc9ff31 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Fri, 24 Feb 2023 10:54:43 +0100 Subject: [PATCH 070/186] fix purge_old_models description in parameter table --- docs/freqai-parameter-table.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/freqai-parameter-table.md b/docs/freqai-parameter-table.md index 7f0b0c213..adcafbc0b 100644 --- a/docs/freqai-parameter-table.md +++ b/docs/freqai-parameter-table.md @@ -15,7 +15,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | `identifier` | **Required.**
A unique ID for the current model. If models are saved to disk, the `identifier` allows for reloading specific pre-trained models/data.
**Datatype:** String. | `live_retrain_hours` | Frequency of retraining during dry/live runs.
**Datatype:** Float > 0.
Default: `0` (models retrain as often as possible). | `expiration_hours` | Avoid making predictions if a model is more than `expiration_hours` old.
**Datatype:** Positive integer.
Default: `0` (models never expire). -| `purge_old_models` | Number of models to keep on disk (not relevant to backtesting). Default is 2, dry/live runs will keep 2 models on disk. Setting to 0 keeps all models. If
**Datatype:** Boolean.
Default: `2`. +| `purge_old_models` | Number of models to keep on disk (not relevant to backtesting). Default is 2, which means that dry/live runs will keep the latest 2 models on disk. Setting to 0 keeps all models. This parameter also accepts a boolean to maintain backwards compatibility.
**Datatype:** Integer.
Default: `2`. | `save_backtest_models` | Save models to disk when running backtesting. Backtesting operates most efficiently by saving the prediction data and reusing them directly for subsequent runs (when you wish to tune entry/exit parameters). Saving backtesting models to disk also allows to use the same model files for starting a dry/live instance with the same model `identifier`.
**Datatype:** Boolean.
Default: `False` (no models are saved). | `fit_live_predictions_candles` | Number of historical candles to use for computing target (label) statistics from prediction data, instead of from the training dataset (more information can be found [here](freqai-configuration.md#creating-a-dynamic-target-threshold)).
**Datatype:** Positive integer. | `continual_learning` | Use the final state of the most recently trained model as starting point for the new model, allowing for incremental learning (more information can be found [here](freqai-running.md#continual-learning)).
**Datatype:** Boolean.
Default: `False`. From cb80d7c26f77c82983f1cb4fed000bf36c94bdc8 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Fri, 24 Feb 2023 11:19:54 +0100 Subject: [PATCH 071/186] close the multi_proc env before creating new ones in an attempt to avoid increasing processes --- .../prediction_models/ReinforcementLearner_multiproc.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py b/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py index 9ee035c95..b3b8c40e6 100644 --- a/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py +++ b/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py @@ -34,6 +34,11 @@ class ReinforcementLearner_multiproc(ReinforcementLearner): train_df = data_dictionary["train_features"] test_df = data_dictionary["test_features"] + if self.train_env: + self.train_env.close() + if self.eval_env: + self.eval_env.close() + env_info = self.pack_env_dict(dk.pair) env_id = "train_env" From 3471f5204b860055054f1fe09027a6c8ec30104f Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 24 Feb 2023 14:34:41 +0100 Subject: [PATCH 072/186] Don't reuse variable --- freqtrade/exchange/exchange.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 9b7cfc5c9..0cac411c7 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2015,9 +2015,9 @@ class Exchange: continue # Deconstruct tuple (has 5 elements) pair, timeframe, c_type, ticks, drop_hint = res - drop_incomplete = drop_hint if drop_incomplete is None else drop_incomplete + drop_incomplete_ = drop_hint if drop_incomplete is None else drop_incomplete ohlcv_df = self._process_ohlcv_df( - pair, timeframe, c_type, ticks, cache, drop_incomplete) + pair, timeframe, c_type, ticks, cache, drop_incomplete_) results_df[(pair, timeframe, c_type)] = ohlcv_df From 7bcae7b6659ee7714eb04fbf30bb7f7d85c1356b Mon Sep 17 00:00:00 2001 From: ASU Date: Sat, 25 Feb 2023 00:26:20 +0200 Subject: [PATCH 073/186] removed redundant dependencies from environment.yml --- environment.yml | 76 ++++--------------------------------------------- 1 file changed, 6 insertions(+), 70 deletions(-) diff --git a/environment.yml b/environment.yml index 5b039e7f7..5193947cf 100644 --- a/environment.yml +++ b/environment.yml @@ -3,73 +3,9 @@ channels: - conda-forge # - defaults dependencies: -# 1/4 req main - - python>=3.8,<=3.10 - - numpy - - pandas - - pip - - - py-find-1st - - aiohttp - - SQLAlchemy - - python-telegram-bot<20.0.0 - - arrow - - cachetools - - requests - - urllib3 - - jsonschema - - TA-Lib - - tabulate - - jinja2 - - blosc - - sdnotify - - fastapi - - uvicorn - - pyjwt - - aiofiles - - psutil - - colorama - - questionary - - prompt-toolkit - - schedule - - python-dateutil - - joblib - - pyarrow - - - # ============================ - # 2/4 req dev - - - coveralls - - flake8 - - mypy - - pytest - - pytest-asyncio - - pytest-cov - - pytest-mock - - isort - - nbconvert - - # ============================ - # 3/4 req hyperopt - - - scipy - - scikit-learn - - filelock - - scikit-optimize - - progressbar2 - # ============================ - # 4/4 req plot - - - plotly - - jupyter - - - pip: - - pycoingecko - # - py_find_1st - - tables - - pytest-random-order - - ccxt - - flake8-tidy-imports - - -e . - # - python-rapidjso + - python>=3.8,<=3.10 + - pip + - libta-lib=0.4.0 # this is required for ta-lib from requirements.txt + - pip: + - -r requirements.txt + - -e . From c6455c41319736e95784832101a954a9e90d0fda Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 13:39:48 +0100 Subject: [PATCH 074/186] Pin scikit-learn to <1.2.0 for conda as well closes #8223 --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 5b039e7f7..afe7a7ed7 100644 --- a/environment.yml +++ b/environment.yml @@ -54,7 +54,7 @@ dependencies: # 3/4 req hyperopt - scipy - - scikit-learn + - scikit-learn<1.2.0 - filelock - scikit-optimize - progressbar2 From dc2cfee056310c5a52bb00c2fdf280b6a70c026b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 13:49:16 +0100 Subject: [PATCH 075/186] Don't request sorted candles from HitBTC. Apparently hitBTC cannot properly handle this anymore. closes #8214 --- freqtrade/exchange/hitbtc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/exchange/hitbtc.py b/freqtrade/exchange/hitbtc.py index a48c9a198..bc4c7aa81 100644 --- a/freqtrade/exchange/hitbtc.py +++ b/freqtrade/exchange/hitbtc.py @@ -19,5 +19,4 @@ class Hitbtc(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 1000, - "ohlcv_params": {"sort": "DESC"} } From 563742f13ce25e1399734d830d77ecba5d609772 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 13:34:19 +0100 Subject: [PATCH 076/186] Fix enum behavior for python 3.11 closes #8221 closes #8217 --- freqtrade/enums/candletype.py | 3 +++ freqtrade/enums/rpcmessagetype.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/freqtrade/enums/candletype.py b/freqtrade/enums/candletype.py index 9d05ff6d7..dcb9f1448 100644 --- a/freqtrade/enums/candletype.py +++ b/freqtrade/enums/candletype.py @@ -13,6 +13,9 @@ class CandleType(str, Enum): FUNDING_RATE = "funding_rate" # BORROW_RATE = "borrow_rate" # * unimplemented + def __str__(self): + return f"{self.name.lower()}" + @staticmethod def from_string(value: str) -> 'CandleType': if not value: diff --git a/freqtrade/enums/rpcmessagetype.py b/freqtrade/enums/rpcmessagetype.py index 2453d16d9..404c75401 100644 --- a/freqtrade/enums/rpcmessagetype.py +++ b/freqtrade/enums/rpcmessagetype.py @@ -37,5 +37,8 @@ class RPCRequestType(str, Enum): WHITELIST = 'whitelist' ANALYZED_DF = 'analyzed_df' + def __str__(self): + return self.value + NO_ECHO_MESSAGES = (RPCMessageType.ANALYZED_DF, RPCMessageType.WHITELIST, RPCMessageType.NEW_CANDLE) From be352ae01499f794ced15af4cfec905f773c4407 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 13:46:14 +0100 Subject: [PATCH 077/186] Update more enums --- freqtrade/enums/signaltype.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/freqtrade/enums/signaltype.py b/freqtrade/enums/signaltype.py index f706fd4dc..b5af1f1b2 100644 --- a/freqtrade/enums/signaltype.py +++ b/freqtrade/enums/signaltype.py @@ -10,6 +10,9 @@ class SignalType(Enum): ENTER_SHORT = "enter_short" EXIT_SHORT = "exit_short" + def __str__(self): + return f"{self.name.lower()}" + class SignalTagType(Enum): """ @@ -18,7 +21,13 @@ class SignalTagType(Enum): ENTER_TAG = "enter_tag" EXIT_TAG = "exit_tag" + def __str__(self): + return f"{self.name.lower()}" + class SignalDirection(str, Enum): LONG = 'long' SHORT = 'short' + + def __str__(self): + return f"{self.name.lower()}" From ff3aa7c1a996e99191b2e315e93bf3300ff91e6b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 16:10:24 +0100 Subject: [PATCH 078/186] Bump Version to 2023.3.dev --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index db339bea3..6ba045adf 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2023.2.dev' +__version__ = '2023.3.dev' if 'dev' in __version__: from pathlib import Path From c8a4a773ee192a7eb495da728181dc738f29ef69 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 24 Feb 2023 18:15:27 +0100 Subject: [PATCH 079/186] Fix _pairs_last_refresh_time storing the wrong date Depending on the drop_incomplete settings, this can lead to implicit bugs --- freqtrade/exchange/exchange.py | 7 +++++-- tests/exchange/test_exchange.py | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 0cac411c7..cdbda1506 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1961,7 +1961,8 @@ class Exchange: cache: bool, drop_incomplete: bool) -> DataFrame: # keeping last candle time as last refreshed time of the pair if ticks and cache: - self._pairs_last_refresh_time[(pair, timeframe, c_type)] = ticks[-1][0] // 1000 + idx = -2 if drop_incomplete and len(ticks) > 1 else -1 + self._pairs_last_refresh_time[(pair, timeframe, c_type)] = ticks[idx][0] // 1000 # keeping parsed dataframe in cache ohlcv_df = ohlcv_to_dataframe(ticks, timeframe, pair=pair, fill_missing=True, drop_incomplete=drop_incomplete) @@ -2034,7 +2035,9 @@ class Exchange: # Timeframe in seconds interval_in_sec = timeframe_to_seconds(timeframe) plr = self._pairs_last_refresh_time.get((pair, timeframe, candle_type), 0) + interval_in_sec - return plr < arrow.utcnow().int_timestamp + # current,active candle open date + now = int(timeframe_to_prev_date(timeframe).timestamp()) + return plr < now @retrier_async async def _async_get_candle_history( diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 13613df37..df878dbd3 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2215,7 +2215,7 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach assert len(res[pair1]) == 99 assert len(res[pair2]) == 99 assert exchange._klines - assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000 + assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-2][0] // 1000 exchange._api_async.fetch_ohlcv.reset_mock() # Returned from cache @@ -2224,7 +2224,7 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach assert len(res) == 2 assert len(res[pair1]) == 99 assert len(res[pair2]) == 99 - assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000 + assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-2][0] // 1000 # Move time 1 candle further but result didn't change yet time_machine.move_to(start + timedelta(hours=101)) @@ -2234,7 +2234,7 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach assert len(res[pair1]) == 99 assert len(res[pair2]) == 99 assert res[pair2].at[0, 'open'] - assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000 + assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-2][0] // 1000 refresh_pior = exchange._pairs_last_refresh_time[pair1] # New candle on exchange - return 100 candles - but skip one candle so we actually get 2 candles @@ -2252,8 +2252,8 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach assert res[pair2].at[0, 'open'] assert refresh_pior != exchange._pairs_last_refresh_time[pair1] - assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000 - assert exchange._pairs_last_refresh_time[pair2] == ohlcv[-1][0] // 1000 + assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-2][0] // 1000 + assert exchange._pairs_last_refresh_time[pair2] == ohlcv[-2][0] // 1000 exchange._api_async.fetch_ohlcv.reset_mock() # Retry same call - from cache From d014e4590e2a5bac5c246b3b1d71ae74c1bff6b6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 17:08:02 +0100 Subject: [PATCH 080/186] use Path.open() instead of open --- freqtrade/configuration/load_config.py | 2 +- freqtrade/data/entryexitanalysis.py | 6 +++--- freqtrade/exchange/binance.py | 2 +- freqtrade/misc.py | 8 ++++---- freqtrade/optimize/backtest_caching.py | 2 +- freqtrade/plugins/pairlist/RemotePairList.py | 2 +- tests/data/test_history.py | 2 +- tests/optimize/test_optimize_reports.py | 4 ++-- tests/test_configuration.py | 4 ++-- tests/test_misc.py | 2 +- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py index a1a77815a..57424468d 100644 --- a/freqtrade/configuration/load_config.py +++ b/freqtrade/configuration/load_config.py @@ -58,7 +58,7 @@ def load_config_file(path: str) -> Dict[str, Any]: """ try: # Read config from stdin if requested in the options - with open(path) if path != '-' else sys.stdin as file: + with Path(path).open() if path != '-' else sys.stdin as file: config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE) except FileNotFoundError: raise OperationalException( diff --git a/freqtrade/data/entryexitanalysis.py b/freqtrade/data/entryexitanalysis.py index b2679bcea..5d67655cd 100644 --- a/freqtrade/data/entryexitanalysis.py +++ b/freqtrade/data/entryexitanalysis.py @@ -24,9 +24,9 @@ def _load_signal_candles(backtest_dir: Path): scpf = Path(backtest_dir.parent / f"{backtest_dir.stem}_signals.pkl") try: - scp = open(scpf, "rb") - signal_candles = joblib.load(scp) - logger.info(f"Loaded signal candles: {str(scpf)}") + with scpf.open("rb") as scp: + signal_candles = joblib.load(scp) + logger.info(f"Loaded signal candles: {str(scpf)}") except Exception as e: logger.error("Cannot load signal candles from pickled results: ", e) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 740d6e8a0..9580bc690 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -195,7 +195,7 @@ class Binance(Exchange): leverage_tiers_path = ( Path(__file__).parent / 'binance_leverage_tiers.json' ) - with open(leverage_tiers_path) as json_file: + with leverage_tiers_path.open() as json_file: return json_load(json_file) else: try: diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 9d9cf38d7..87cea54c0 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -81,7 +81,7 @@ def file_dump_json(filename: Path, data: Any, is_zip: bool = False, log: bool = else: if log: logger.info(f'dumping json to "{filename}"') - with open(filename, 'w') as fp: + with filename.open('w') as fp: rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE) logger.debug(f'done json to "{filename}"') @@ -98,7 +98,7 @@ def file_dump_joblib(filename: Path, data: Any, log: bool = True) -> None: if log: logger.info(f'dumping joblib to "{filename}"') - with open(filename, 'wb') as fp: + with filename.open('wb') as fp: joblib.dump(data, fp) logger.debug(f'done joblib dump to "{filename}"') @@ -112,7 +112,7 @@ def json_load(datafile: IO) -> Any: return rapidjson.load(datafile, number_mode=rapidjson.NM_NATIVE) -def file_load_json(file): +def file_load_json(file: Path): if file.suffix != ".gz": gzipfile = file.with_suffix(file.suffix + '.gz') @@ -125,7 +125,7 @@ def file_load_json(file): pairdata = json_load(datafile) elif file.is_file(): logger.debug(f"Loading historical data from file {file}") - with open(file) as datafile: + with file.open() as datafile: pairdata = json_load(datafile) else: return None diff --git a/freqtrade/optimize/backtest_caching.py b/freqtrade/optimize/backtest_caching.py index d9d270072..f34bbffef 100644 --- a/freqtrade/optimize/backtest_caching.py +++ b/freqtrade/optimize/backtest_caching.py @@ -29,7 +29,7 @@ def get_strategy_run_id(strategy) -> str: # Include _ft_params_from_file - so changing parameter files cause cache eviction digest.update(rapidjson.dumps( strategy._ft_params_from_file, default=str, number_mode=rapidjson.NM_NAN).encode('utf-8')) - with open(strategy.__file__, 'rb') as fp: + with Path(strategy.__file__).open('rb') as fp: digest.update(fp.read()) return digest.hexdigest().lower() diff --git a/freqtrade/plugins/pairlist/RemotePairList.py b/freqtrade/plugins/pairlist/RemotePairList.py index b54be1fa7..764c16f1a 100644 --- a/freqtrade/plugins/pairlist/RemotePairList.py +++ b/freqtrade/plugins/pairlist/RemotePairList.py @@ -157,7 +157,7 @@ class RemotePairList(IPairList): file_path = Path(filename) if file_path.exists(): - with open(filename) as json_file: + with file_path.open() as json_file: # Load the JSON data into a dictionary jsonparse = json.load(json_file) diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 7d313c446..5cd7327fd 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -191,7 +191,7 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None: test_data = None test_filename = testdatadir.joinpath('UNITTEST_BTC-1m.json') - with open(test_filename, "rt") as file: + with test_filename.open("rt") as file: test_data = json.load(file) test_data_df = ohlcv_to_dataframe(test_data, '1m', 'UNITTEST/BTC', diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 549202284..f71e6c492 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -255,7 +255,7 @@ def test_write_read_backtest_candles(tmpdir): # test directory exporting stored_file = store_backtest_signal_candles(Path(tmpdir), candle_dict, '2022_01_01_15_05_13') - scp = open(stored_file, "rb") + scp = stored_file.open("rb") pickled_signal_candles = joblib.load(scp) scp.close() @@ -269,7 +269,7 @@ 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 = open(stored_file, "rb") + scp = stored_file.open("rb") pickled_signal_candles = joblib.load(scp) scp.close() diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 4a94a3c2e..357e16702 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -59,7 +59,7 @@ def test_load_config_incorrect_stake_amount(default_conf) -> None: def test_load_config_file(default_conf, mocker, caplog) -> None: del default_conf['user_data_dir'] default_conf['datadir'] = str(default_conf['datadir']) - file_mock = mocker.patch('freqtrade.configuration.load_config.open', mocker.mock_open( + file_mock = mocker.patch('freqtrade.configuration.load_config.Path.open', mocker.mock_open( read_data=json.dumps(default_conf) )) @@ -272,7 +272,7 @@ def test_load_config_max_open_trades_minus_one(default_conf, mocker, caplog) -> def test_load_config_file_exception(mocker) -> None: mocker.patch( - 'freqtrade.configuration.configuration.open', + 'freqtrade.configuration.configuration.Path.open', MagicMock(side_effect=FileNotFoundError('File not found')) ) diff --git a/tests/test_misc.py b/tests/test_misc.py index 596c7bd51..6b4343ab2 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -46,7 +46,7 @@ def test_shorten_date() -> None: def test_file_dump_json(mocker) -> None: - file_open = mocker.patch('freqtrade.misc.open', MagicMock()) + file_open = mocker.patch('freqtrade.misc.Path.open', MagicMock()) json_dump = mocker.patch('rapidjson.dump', MagicMock()) file_dump_json(Path('somefile'), [1, 2, 3]) assert file_open.call_count == 1 From 26315b6bc25c75e6d2d0cc78be9e82fdaa177538 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 17:17:05 +0100 Subject: [PATCH 081/186] add PTH ruff selection --- freqtrade/freqai/data_drawer.py | 32 ++++++++++++++++---------------- freqtrade/freqai/utils.py | 2 +- pyproject.toml | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index c90bb23fc..03e5ba56e 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -126,7 +126,7 @@ class FreqaiDataDrawer: """ exists = self.global_metadata_path.is_file() if exists: - with open(self.global_metadata_path, "r") as fp: + with self.global_metadata_path.open("r") as fp: metatada_dict = rapidjson.load(fp, number_mode=rapidjson.NM_NATIVE) return metatada_dict return {} @@ -139,7 +139,7 @@ class FreqaiDataDrawer: """ exists = self.pair_dictionary_path.is_file() if exists: - with open(self.pair_dictionary_path, "r") as fp: + with self.pair_dictionary_path.open("r") as fp: self.pair_dict = rapidjson.load(fp, number_mode=rapidjson.NM_NATIVE) else: logger.info("Could not find existing datadrawer, starting from scratch") @@ -152,7 +152,7 @@ class FreqaiDataDrawer: if self.freqai_info.get('write_metrics_to_disk', False): exists = self.metric_tracker_path.is_file() if exists: - with open(self.metric_tracker_path, "r") as fp: + with self.metric_tracker_path.open("r") as fp: self.metric_tracker = rapidjson.load(fp, number_mode=rapidjson.NM_NATIVE) logger.info("Loading existing metric tracker from disk.") else: @@ -166,7 +166,7 @@ class FreqaiDataDrawer: exists = self.historic_predictions_path.is_file() if exists: try: - with open(self.historic_predictions_path, "rb") as fp: + with self.historic_predictions_path.open("rb") as fp: self.historic_predictions = cloudpickle.load(fp) logger.info( f"Found existing historic predictions at {self.full_path}, but beware " @@ -176,7 +176,7 @@ class FreqaiDataDrawer: except EOFError: logger.warning( 'Historical prediction file was corrupted. Trying to load backup file.') - with open(self.historic_predictions_bkp_path, "rb") as fp: + with self.historic_predictions_bkp_path.open("rb") as fp: self.historic_predictions = cloudpickle.load(fp) logger.warning('FreqAI successfully loaded the backup historical predictions file.') @@ -189,7 +189,7 @@ class FreqaiDataDrawer: """ Save historic predictions pickle to disk """ - with open(self.historic_predictions_path, "wb") as fp: + with self.historic_predictions_path.open("wb") as fp: cloudpickle.dump(self.historic_predictions, fp, protocol=cloudpickle.DEFAULT_PROTOCOL) # create a backup @@ -200,16 +200,16 @@ class FreqaiDataDrawer: Save metric tracker of all pair metrics collected. """ with self.save_lock: - with open(self.metric_tracker_path, 'w') as fp: + with self.metric_tracker_path.open('w') as fp: rapidjson.dump(self.metric_tracker, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE) - def save_drawer_to_disk(self): + def save_drawer_to_disk(self) -> None: """ Save data drawer full of all pair model metadata in present model folder. """ with self.save_lock: - with open(self.pair_dictionary_path, 'w') as fp: + with self.pair_dictionary_path.open('w') as fp: rapidjson.dump(self.pair_dict, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE) @@ -218,7 +218,7 @@ class FreqaiDataDrawer: Save global metadata json to disk """ with self.save_lock: - with open(self.global_metadata_path, 'w') as fp: + with self.global_metadata_path.open('w') as fp: rapidjson.dump(metadata, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE) @@ -424,7 +424,7 @@ class FreqaiDataDrawer: dk.data["training_features_list"] = list(dk.data_dictionary["train_features"].columns) dk.data["label_list"] = dk.label_list - with open(save_path / f"{dk.model_filename}_metadata.json", "w") as fp: + with (save_path / f"{dk.model_filename}_metadata.json").open("w") as fp: rapidjson.dump(dk.data, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE) return @@ -457,7 +457,7 @@ class FreqaiDataDrawer: dk.data["training_features_list"] = dk.training_features_list dk.data["label_list"] = dk.label_list # store the metadata - with open(save_path / f"{dk.model_filename}_metadata.json", "w") as fp: + with (save_path / f"{dk.model_filename}_metadata.json").open("w") as fp: rapidjson.dump(dk.data, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE) # save the train data to file so we can check preds for area of applicability later @@ -471,7 +471,7 @@ class FreqaiDataDrawer: if self.freqai_info["feature_parameters"].get("principal_component_analysis"): cloudpickle.dump( - dk.pca, open(dk.data_path / f"{dk.model_filename}_pca_object.pkl", "wb") + dk.pca, (dk.data_path / f"{dk.model_filename}_pca_object.pkl").open("wb") ) self.model_dictionary[coin] = model @@ -491,7 +491,7 @@ class FreqaiDataDrawer: Load only metadata into datakitchen to increase performance during presaved backtesting (prediction file loading). """ - with open(dk.data_path / f"{dk.model_filename}_metadata.json", "r") as fp: + with (dk.data_path / f"{dk.model_filename}_metadata.json").open("r") as fp: dk.data = rapidjson.load(fp, number_mode=rapidjson.NM_NATIVE) dk.training_features_list = dk.data["training_features_list"] dk.label_list = dk.data["label_list"] @@ -514,7 +514,7 @@ class FreqaiDataDrawer: dk.data = self.meta_data_dictionary[coin]["meta_data"] dk.data_dictionary["train_features"] = self.meta_data_dictionary[coin]["train_df"] else: - with open(dk.data_path / f"{dk.model_filename}_metadata.json", "r") as fp: + with (dk.data_path / f"{dk.model_filename}_metadata.json").open("r") as fp: dk.data = rapidjson.load(fp, number_mode=rapidjson.NM_NATIVE) dk.data_dictionary["train_features"] = pd.read_pickle( @@ -552,7 +552,7 @@ class FreqaiDataDrawer: if self.config["freqai"]["feature_parameters"]["principal_component_analysis"]: dk.pca = cloudpickle.load( - open(dk.data_path / f"{dk.model_filename}_pca_object.pkl", "rb") + (dk.data_path / f"{dk.model_filename}_pca_object.pkl").open("rb") ) return model diff --git a/freqtrade/freqai/utils.py b/freqtrade/freqai/utils.py index 806e3ca15..2ba49ac40 100644 --- a/freqtrade/freqai/utils.py +++ b/freqtrade/freqai/utils.py @@ -211,7 +211,7 @@ def record_params(config: Dict[str, Any], full_path: Path) -> None: "pairs": config.get('exchange', {}).get('pair_whitelist') } - with open(params_record_path, "w") as handle: + with params_record_path.open("w") as handle: rapidjson.dump( run_params, handle, diff --git a/pyproject.toml b/pyproject.toml index 8a7750731..f53802dc6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,5 +68,5 @@ extend-select = [ # "DTZ", # flake8-datetimez # "RSE", # flake8-raise # "TCH", # flake8-type-checking - # "PTH", # flake8-use-pathlib + "PTH", # flake8-use-pathlib ] From 32ce819889c776192e12c975a4e3c507ab572843 Mon Sep 17 00:00:00 2001 From: ASU Date: Sat, 25 Feb 2023 18:23:07 +0200 Subject: [PATCH 082/186] Removed environment.yml and updated documentation --- docs/installation.md | 18 +++++++----------- environment.yml | 11 ----------- 2 files changed, 7 insertions(+), 22 deletions(-) delete mode 100644 environment.yml diff --git a/docs/installation.md b/docs/installation.md index 1c0aed7ba..6e8488b9f 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -290,10 +290,8 @@ cd freqtrade #### Freqtrade install: Conda Environment -Prepare conda-freqtrade environment, using file `environment.yml`, which exist in main freqtrade directory - ```bash -conda env create -n freqtrade-conda -f environment.yml +conda create --name freqtrade python=3.10 ``` !!! Note "Creating Conda Environment" @@ -302,12 +300,9 @@ conda env create -n freqtrade-conda -f environment.yml ```bash # choose your own packages conda env create -n [name of the environment] [python version] [packages] - - # point to file with packages - conda env create -n [name of the environment] -f [file] ``` -#### Enter/exit freqtrade-conda environment +#### Enter/exit freqtrade environment To check available environments, type @@ -319,7 +314,7 @@ Enter installed environment ```bash # enter conda environment -conda activate freqtrade-conda +conda activate freqtrade # exit conda environment - don't do it now conda deactivate @@ -329,6 +324,7 @@ Install last python dependencies with pip ```bash python3 -m pip install --upgrade pip +python3 -m pip install -r requirements.txt python3 -m pip install -e . ``` @@ -336,7 +332,7 @@ Patch conda libta-lib (Linux only) ```bash # Ensure that the environment is active! -conda activate freqtrade-conda +conda activate freqtrade cd build_helpers bash install_ta-lib.sh ${CONDA_PREFIX} nosudo @@ -355,8 +351,8 @@ conda env list # activate base environment conda activate -# activate freqtrade-conda environment -conda activate freqtrade-conda +# activate freqtrade environment +conda activate freqtrade #deactivate any conda environments conda deactivate diff --git a/environment.yml b/environment.yml deleted file mode 100644 index 5193947cf..000000000 --- a/environment.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: freqtrade -channels: - - conda-forge -# - defaults -dependencies: - - python>=3.8,<=3.10 - - pip - - libta-lib=0.4.0 # this is required for ta-lib from requirements.txt - - pip: - - -r requirements.txt - - -e . From 84d905a648040783d49d7293db1662605977cfa9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 17:39:18 +0100 Subject: [PATCH 083/186] Fix missed test --- tests/test_configuration.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 357e16702..aab868bec 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -73,7 +73,8 @@ def test_load_config_file_error(default_conf, mocker, caplog) -> None: default_conf['datadir'] = str(default_conf['datadir']) filedata = json.dumps(default_conf).replace( '"stake_amount": 0.001,', '"stake_amount": .001,') - mocker.patch('freqtrade.configuration.load_config.open', mocker.mock_open(read_data=filedata)) + mocker.patch('freqtrade.configuration.load_config.Path.open', + mocker.mock_open(read_data=filedata)) mocker.patch.object(Path, "read_text", MagicMock(return_value=filedata)) with pytest.raises(OperationalException, match=r".*Please verify the following segment.*"): From 305eda74e2e8ccff87b727cc6c034b4b5ded19b0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 20:50:26 +0100 Subject: [PATCH 084/186] Enable Complexity for ruff --- freqtrade/commands/data_commands.py | 22 +++++++++++++--------- pyproject.toml | 7 +++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index 2cd736b3e..1e74e1036 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -5,7 +5,7 @@ from datetime import datetime, timedelta from typing import Any, Dict, List from freqtrade.configuration import TimeRange, setup_utils_configuration -from freqtrade.constants import DATETIME_PRINT_FORMAT +from freqtrade.constants import DATETIME_PRINT_FORMAT, Config from freqtrade.data.converter import convert_ohlcv_format, convert_trades_format from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_ohlcv_data, refresh_backtest_trades_data) @@ -20,15 +20,24 @@ from freqtrade.util.binance_mig import migrate_binance_futures_data logger = logging.getLogger(__name__) +def _data_download_sanity(config: Config) -> None: + if 'days' in config and 'timerange' in config: + raise OperationalException("--days and --timerange are mutually exclusive. " + "You can only specify one or the other.") + + if 'pairs' not in config: + raise OperationalException( + "Downloading data requires a list of pairs. " + "Please check the documentation on how to configure this.") + + def start_download_data(args: Dict[str, Any]) -> None: """ Download data (former download_backtest_data.py script) """ config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) - if 'days' in config and 'timerange' in config: - raise OperationalException("--days and --timerange are mutually exclusive. " - "You can only specify one or the other.") + _data_download_sanity(config) timerange = TimeRange() if 'days' in config: time_since = (datetime.now() - timedelta(days=config['days'])).strftime("%Y%m%d") @@ -40,11 +49,6 @@ def start_download_data(args: Dict[str, Any]) -> None: # Remove stake-currency to skip checks which are not relevant for datadownload config['stake_currency'] = '' - if 'pairs' not in config: - raise OperationalException( - "Downloading data requires a list of pairs. " - "Please check the documentation on how to configure this.") - pairs_not_available: List[str] = [] # Init exchange diff --git a/pyproject.toml b/pyproject.toml index 8a7750731..698a621b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,11 @@ ignore = ["freqtrade/vendor/**"] [tool.ruff] line-length = 100 extend-exclude = [".env"] +target-version = "py38" extend-select = [ + "C90", # mccabe + # "N", # pep8-naming + # "UP", # pyupgrade "TID", # flake8-tidy-imports # "EXE", # flake8-executable "YTT", # flake8-2020 @@ -70,3 +74,6 @@ extend-select = [ # "TCH", # flake8-type-checking # "PTH", # flake8-use-pathlib ] + +[tool.ruff.mccabe] +max-complexity = 12 From e88bb4e05ca7cdd17656fe6aa118631c828e501d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 26 Feb 2023 15:09:25 +0100 Subject: [PATCH 085/186] Revert small change - otherwise the data is never updated. --- freqtrade/freqai/data_drawer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index 883b9d94b..356100aea 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -661,7 +661,7 @@ class FreqaiDataDrawer: f"ends at {df_dp['date'].iloc[0]}." ) - hist_df = pd.concat( + history_data[pair][tf] = pd.concat( [ hist_df, df_dp.iloc[index:], From 79dc972e5a41e8b60733f30dd164e74702e7b4c2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 26 Feb 2023 15:25:54 +0100 Subject: [PATCH 086/186] Add explicit test for kucoin --- tests/exchange/test_kucoin.py | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/exchange/test_kucoin.py b/tests/exchange/test_kucoin.py index 65c855b7a..0a57d728c 100644 --- a/tests/exchange/test_kucoin.py +++ b/tests/exchange/test_kucoin.py @@ -125,3 +125,45 @@ def test_stoploss_adjust_kucoin(mocker, default_conf): # Test with invalid order case order['stopPrice'] = None assert exchange.stoploss_adjust(1501, order, 'sell') + + +@pytest.mark.parametrize("side", ["buy", "sell"]) +@pytest.mark.parametrize("ordertype,rate", [ + ("market", None), + ("market", 200), + ("limit", 200), + ("stop_loss_limit", 200) +]) +def test_kucoin_create_order(default_conf, mocker, side, ordertype, rate): + api_mock = MagicMock() + order_id = 'test_prod_{}_{}'.format(side, randint(0, 10 ** 6)) + api_mock.create_order = MagicMock(return_value={ + 'id': order_id, + 'info': { + 'foo': 'bar' + }, + 'symbol': 'XRP/USDT', + 'amount': 1 + }) + default_conf['dry_run'] = False + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id='kucoin') + exchange._set_leverage = MagicMock() + exchange.set_margin_mode = MagicMock() + + order = exchange.create_order( + pair='XRP/USDT', + ordertype=ordertype, + side=side, + amount=1, + rate=rate, + leverage=1.0 + ) + + assert 'id' in order + assert 'info' in order + assert order['id'] == order_id + assert order['amount'] == 1 + # Status must be faked to open for kucoin. + assert order['status'] == 'open' From 27676f4aa291b9433be89a322ab7c926ddbe2912 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 26 Feb 2023 15:30:37 +0100 Subject: [PATCH 087/186] Add explicit bybit test --- tests/exchange/test_exchange.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index df878dbd3..828bba16f 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -27,7 +27,7 @@ from tests.conftest import (generate_test_data_raw, get_mock_coro, get_patched_e # Make sure to always keep one exchange here which is NOT subclassed!! -EXCHANGES = ['bittrex', 'binance', 'kraken', 'gate'] +EXCHANGES = ['bittrex', 'binance', 'kraken', 'gate', 'bybit'] get_entry_rate_data = [ ('other', 20, 19, 10, 0.0, 20), # Full ask side @@ -5015,7 +5015,7 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers): exchange.get_max_leverage("BTC/USDT:USDT", 1000000000.01) -@pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'gate', 'okx']) +@pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'gate', 'okx', 'bybit']) def test__get_params(mocker, default_conf, exchange_name): api_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) @@ -5036,6 +5036,9 @@ def test__get_params(mocker, default_conf, exchange_name): params2['tdMode'] = 'isolated' params2['posSide'] = 'net' + if exchange_name == 'bybit': + params2['position_idx'] = 0 + assert exchange._get_params( side="buy", ordertype='market', From 6f7ab97fc363daa90a9c1767498a80192f69edef Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 26 Feb 2023 15:41:42 +0100 Subject: [PATCH 088/186] Improve bybit test coverage --- tests/exchange/test_bybit.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/exchange/test_bybit.py b/tests/exchange/test_bybit.py index 7c8324bf6..d0d5114a1 100644 --- a/tests/exchange/test_bybit.py +++ b/tests/exchange/test_bybit.py @@ -1,3 +1,4 @@ +from datetime import datetime, timezone from unittest.mock import MagicMock from freqtrade.enums.marginmode import MarginMode @@ -55,3 +56,19 @@ async def test_bybit_fetch_funding_rate(default_conf, mocker): kwargs = api_mock.fetch_funding_rate_history.call_args_list[0][1] assert kwargs['params'] == {'until': since_ms_end} assert kwargs['since'] == since_ms + + +def test_bybit_get_funding_fees(default_conf, mocker): + now = datetime.now(timezone.utc) + exchange = get_patched_exchange(mocker, default_conf, id='bybit') + exchange._fetch_and_calculate_funding_fees = MagicMock() + exchange.get_funding_fees('BTC/USDT:USDT', 1, False, now) + assert exchange._fetch_and_calculate_funding_fees.call_count == 0 + + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + exchange = get_patched_exchange(mocker, default_conf, id='bybit') + exchange._fetch_and_calculate_funding_fees = MagicMock() + exchange.get_funding_fees('BTC/USDT:USDT', 1, False, now) + + assert exchange._fetch_and_calculate_funding_fees.call_count == 1 From 5b0bc5bbc54d7122f677a8ff9b615238e7940622 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 26 Feb 2023 16:11:47 +0100 Subject: [PATCH 089/186] Don't "fix" dry-run kucoin orders closes #8229 --- freqtrade/exchange/kucoin.py | 5 +++-- tests/exchange/test_exchange.py | 11 ++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/freqtrade/exchange/kucoin.py b/freqtrade/exchange/kucoin.py index 797d9fbd2..20e558513 100644 --- a/freqtrade/exchange/kucoin.py +++ b/freqtrade/exchange/kucoin.py @@ -64,6 +64,7 @@ class Kucoin(Exchange): # ccxt returns status = 'closed' at the moment - which is information ccxt invented. # Since we rely on status heavily, we must set it to 'open' here. # ref: https://github.com/ccxt/ccxt/pull/16674, (https://github.com/ccxt/ccxt/pull/16553) - res['type'] = ordertype - res['status'] = 'open' + if not self._config['dry_run']: + res['type'] = ordertype + res['status'] = 'open' return res diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 828bba16f..7ccd32155 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -27,7 +27,7 @@ from tests.conftest import (generate_test_data_raw, get_mock_coro, get_patched_e # Make sure to always keep one exchange here which is NOT subclassed!! -EXCHANGES = ['bittrex', 'binance', 'kraken', 'gate', 'bybit'] +EXCHANGES = ['bittrex', 'binance', 'kraken', 'gate', 'kucoin', 'bybit'] get_entry_rate_data = [ ('other', 20, 19, 10, 0.0, 20), # Full ask side @@ -1269,7 +1269,7 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill fetch_l2_order_book=order_book_l2_usd, ) - order = exchange.create_dry_run_order( + order = exchange.create_order( pair='LTC/USDT', ordertype='limit', side=side, @@ -1332,7 +1332,7 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou fetch_l2_order_book=order_book_l2_usd, ) - order = exchange.create_dry_run_order( + order = exchange.create_order( pair='LTC/USDT', ordertype='market', side=side, @@ -1425,9 +1425,10 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, assert order['amount'] == 0.01 -def test_buy_dry_run(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_buy_dry_run(default_conf, mocker, exchange_name): default_conf['dry_run'] = True - exchange = get_patched_exchange(mocker, default_conf) + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) order = exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy", amount=1, rate=200, leverage=1.0, From 533f97f0801065d6c82da5b71815f40bffe66fa0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 03:56:50 +0000 Subject: [PATCH 090/186] Bump pydantic from 1.10.4 to 1.10.5 Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.4 to 1.10.5. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/v1.10.5/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v1.10.4...v1.10.5) --- updated-dependencies: - dependency-name: pydantic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 855aa664d..da990721f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,7 +35,7 @@ sdnotify==0.3.2 # API Server fastapi==0.92.0 -pydantic==1.10.4 +pydantic==1.10.5 uvicorn==0.20.0 pyjwt==2.6.0 aiofiles==23.1.0 From cc78054b8c70f2de6b5ca96008f2773c61139787 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 03:56:57 +0000 Subject: [PATCH 091/186] Bump types-python-dateutil from 2.8.19.6 to 2.8.19.8 Bumps [types-python-dateutil](https://github.com/python/typeshed) from 2.8.19.6 to 2.8.19.8. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-python-dateutil dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c23447694..97b5f3e7e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -29,4 +29,4 @@ types-cachetools==5.3.0.0 types-filelock==3.2.7 types-requests==2.28.11.13 types-tabulate==0.9.0.0 -types-python-dateutil==2.8.19.6 +types-python-dateutil==2.8.19.8 From 7add902bc7c47067b3aacba8e7071ef9c5855095 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 03:57:04 +0000 Subject: [PATCH 092/186] Bump pre-commit from 3.0.4 to 3.1.0 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.0.4 to 3.1.0. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v3.0.4...v3.1.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c23447694..4a5a959f2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,7 +9,7 @@ coveralls==3.3.1 ruff==0.0.251 mypy==1.0.1 -pre-commit==3.0.4 +pre-commit==3.1.0 pytest==7.2.1 pytest-asyncio==0.20.3 pytest-cov==4.0.0 From 2a7f86bfb4834d83f73d8ac0abae65a5fcd9310a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 03:57:22 +0000 Subject: [PATCH 093/186] Bump ruff from 0.0.251 to 0.0.252 Bumps [ruff](https://github.com/charliermarsh/ruff) from 0.0.251 to 0.0.252. - [Release notes](https://github.com/charliermarsh/ruff/releases) - [Changelog](https://github.com/charliermarsh/ruff/blob/main/BREAKING_CHANGES.md) - [Commits](https://github.com/charliermarsh/ruff/compare/v0.0.251...v0.0.252) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c23447694..a0d362f55 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==3.3.1 -ruff==0.0.251 +ruff==0.0.252 mypy==1.0.1 pre-commit==3.0.4 pytest==7.2.1 From a4423778d552e843d922fa5d285b7af693ab1999 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 03:58:01 +0000 Subject: [PATCH 094/186] Bump mkdocs-material from 9.0.13 to 9.0.15 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.0.13 to 9.0.15. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.0.13...9.0.15) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index f5e671e88..065411018 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.3.7 mkdocs==1.4.2 -mkdocs-material==9.0.13 +mkdocs-material==9.0.15 mdx_truly_sane_lists==1.3 pymdown-extensions==9.9.2 jinja2==3.1.2 From 05f3884722404b71b499c18a99fca72fa3419b67 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 27 Feb 2023 06:25:13 +0100 Subject: [PATCH 095/186] bump pre-commit dateutil --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 58f526ce9..0936fff74 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - types-filelock==3.2.7 - types-requests==2.28.11.13 - types-tabulate==0.9.0.0 - - types-python-dateutil==2.8.19.6 + - types-python-dateutil==2.8.19.8 # stages: [push] - repo: https://github.com/pycqa/isort From e83eefb71d1197891c982cf466929082a28f8b0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 06:10:08 +0000 Subject: [PATCH 096/186] Bump types-requests from 2.28.11.13 to 2.28.11.15 Bumps [types-requests](https://github.com/python/typeshed) from 2.28.11.13 to 2.28.11.15. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ad9d303cf..19130ec8e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -27,6 +27,6 @@ nbconvert==7.2.9 # mypy types types-cachetools==5.3.0.0 types-filelock==3.2.7 -types-requests==2.28.11.13 +types-requests==2.28.11.15 types-tabulate==0.9.0.0 types-python-dateutil==2.8.19.8 From 48b21d00d2afb8705a3c78259d39c007c35365a5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 27 Feb 2023 07:12:12 +0100 Subject: [PATCH 097/186] bump pre-commit requests --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0936fff74..436d7bc09 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: additional_dependencies: - types-cachetools==5.3.0.0 - types-filelock==3.2.7 - - types-requests==2.28.11.13 + - types-requests==2.28.11.15 - types-tabulate==0.9.0.0 - types-python-dateutil==2.8.19.8 # stages: [push] From 201522f1b170de855cbb33d9b7d21060a31ea31f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 07:10:51 +0000 Subject: [PATCH 098/186] Bump types-tabulate from 0.9.0.0 to 0.9.0.1 Bumps [types-tabulate](https://github.com/python/typeshed) from 0.9.0.0 to 0.9.0.1. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-tabulate dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 19130ec8e..f45daaabd 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -28,5 +28,5 @@ nbconvert==7.2.9 types-cachetools==5.3.0.0 types-filelock==3.2.7 types-requests==2.28.11.15 -types-tabulate==0.9.0.0 +types-tabulate==0.9.0.1 types-python-dateutil==2.8.19.8 From 81bc515e5ddac9f11d8d280b8b3a81290c0d5142 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 27 Feb 2023 10:00:41 +0100 Subject: [PATCH 099/186] Bump tabulate types for pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 436d7bc09..8711a65dc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - types-cachetools==5.3.0.0 - types-filelock==3.2.7 - types-requests==2.28.11.15 - - types-tabulate==0.9.0.0 + - types-tabulate==0.9.0.1 - types-python-dateutil==2.8.19.8 # stages: [push] From 1d5608d627966d4fae47ba11b5aff416f5961f83 Mon Sep 17 00:00:00 2001 From: ASU Date: Mon, 27 Feb 2023 12:14:38 +0200 Subject: [PATCH 100/186] Fix last_process related bug in RPC.health --- freqtrade/freqtradebot.py | 2 +- freqtrade/rpc/api_server/api_schemas.py | 4 ++-- freqtrade/rpc/api_server/api_v1.py | 2 +- freqtrade/rpc/rpc.py | 17 ++++++++++++----- freqtrade/rpc/telegram.py | 2 +- tests/rpc/test_rpc.py | 6 +++--- tests/rpc/test_rpc_apiserver.py | 4 ++-- 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 82be6f3b5..6529037e8 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -127,7 +127,7 @@ class FreqtradeBot(LoggingMixin): for minutes in [0, 15, 30, 45]: t = str(time(time_slot, minutes, 2)) self._schedule.every().day.at(t).do(update) - self.last_process = datetime(1970, 1, 1, tzinfo=timezone.utc) + self.last_process: Optional[datetime] = None self.strategy.ft_bot_start() # Initialize protections AFTER bot start - otherwise parameters are not loaded. diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 58f6ad583..b9595a3dd 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -456,5 +456,5 @@ class SysInfo(BaseModel): class Health(BaseModel): - last_process: datetime - last_process_ts: int + last_process: Optional[datetime] + last_process_ts: Optional[int] diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 73bdde86b..f6bab3624 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -346,4 +346,4 @@ def sysinfo(): @router.get('/health', response_model=Health, tags=['info']) def health(rpc: RPC = Depends(get_rpc)): - return rpc._health() + return rpc.health() diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 83bffb779..08bf8d5c8 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -89,7 +89,7 @@ class RPC: # Bind _fiat_converter if needed _fiat_converter: Optional[CryptoToFiatConverter] = None - def __init__(self, freqtrade) -> None: + def __init__(self, freqtrade: "FreqtradeBot") -> None: """ Initializes all enabled rpc modules :param freqtrade: Instance of a freqtrade bot @@ -1198,10 +1198,17 @@ class RPC: "ram_pct": psutil.virtual_memory().percent } - def _health(self) -> Dict[str, Union[str, int]]: + def health(self) -> Dict[str, Optional[Union[str, int]]]: last_p = self._freqtrade.last_process + if last_p is None: + return { + "last_process": None, + "last_process_loc": None, + "last_process_ts": None, + } + return { - 'last_process': str(last_p), - 'last_process_loc': last_p.astimezone(tzlocal()).strftime(DATETIME_PRINT_FORMAT), - 'last_process_ts': int(last_p.timestamp()), + "last_process": str(last_p), + "last_process_loc": last_p.astimezone(tzlocal()).strftime(DATETIME_PRINT_FORMAT), + "last_process_ts": int(last_p.timestamp()), } diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index fbd675d02..09032f10d 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1527,7 +1527,7 @@ class Telegram(RPCHandler): Handler for /health Shows the last process timestamp """ - health = self._rpc._health() + health = self._rpc.health() message = f"Last process: `{health['last_process_loc']}`" self._send_msg(message) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 31e19ce3f..d9b7c764a 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -1252,6 +1252,6 @@ def test_rpc_health(mocker, default_conf) -> None: freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc = RPC(freqtradebot) - result = rpc._health() - assert result['last_process'] == '1970-01-01 00:00:00+00:00' - assert result['last_process_ts'] == 0 + result = rpc.health() + assert result['last_process'] is None + assert result['last_process_ts'] is None diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 43d9abb78..9c6f33046 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1801,8 +1801,8 @@ def test_health(botclient): assert_response(rc) ret = rc.json() - assert ret['last_process_ts'] == 0 - assert ret['last_process'] == '1970-01-01T00:00:00+00:00' + assert ret["last_process_ts"] is None + assert ret["last_process"] is None def test_api_ws_subscribe(botclient, mocker): From bcd416c83d7f6abe7adb99f0653dbe1c8bcef5dc Mon Sep 17 00:00:00 2001 From: ASU Date: Mon, 27 Feb 2023 16:18:24 +0200 Subject: [PATCH 101/186] Removed unresolved FreqTrade typehint --- freqtrade/rpc/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 08bf8d5c8..949ace595 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -89,7 +89,7 @@ class RPC: # Bind _fiat_converter if needed _fiat_converter: Optional[CryptoToFiatConverter] = None - def __init__(self, freqtrade: "FreqtradeBot") -> None: + def __init__(self, freqtrade) -> None: """ Initializes all enabled rpc modules :param freqtrade: Instance of a freqtrade bot From 02c831a4e7c0316e46f911eca2beb5917c982ce7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 27 Feb 2023 18:04:21 +0100 Subject: [PATCH 102/186] Improve Note wording closes #8235 --- docs/strategy-customization.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 462f20402..3519a80cd 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -954,12 +954,14 @@ In some situations it may be confusing to deal with stops relative to current ra ## Additional data (Wallets) -The strategy provides access to the `Wallets` object. This contains the current balances on the exchange. +The strategy provides access to the `wallets` object. This contains the current balances on the exchange. -!!! Note - Wallets is not available during backtesting / hyperopt. +!!! Note "Backtesting / Hyperopt" + Wallets behaves differently depending on the function it's called. + Within `populate_*()` methods, it'll return the full wallet as configured. + Within [callbacks](strategy-callbacks.md), you'll get the wallet state corresponding to the actual simulated wallet at that point in the simulation process. -Please always check if `Wallets` is available to avoid failures during backtesting. +Please always check if `wallets` is available to avoid failures during backtesting. ``` python if self.wallets: From 87fe4108a252ca31cbb0200a452ef283a41e8c35 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 27 Feb 2023 18:24:19 +0100 Subject: [PATCH 103/186] Fix order numeration to also work with stoploss on exchange --- freqtrade/rpc/telegram.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index fbd675d02..d5c76bdb7 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -469,26 +469,27 @@ class Telegram(RPCHandler): lines_detail: List[str] = [] if len(filled_orders) > 0: first_avg = filled_orders[0]["safe_price"] - - for x, order in enumerate(filled_orders): + order_nr = 0 + for order in filled_orders: lines: List[str] = [] if order['is_open'] is True: continue + order_nr += 1 wording = 'Entry' if order['ft_is_entry'] else 'Exit' cur_entry_datetime = arrow.get(order["order_filled_date"]) cur_entry_amount = order["filled"] or order["amount"] cur_entry_average = order["safe_price"] lines.append(" ") - if x == 0: - lines.append(f"*{wording} #{x+1}:*") + if order_nr == 1: + lines.append(f"*{wording} #{order_nr}:*") lines.append( f"*Amount:* {cur_entry_amount} ({order['cost']:.8f} {quote_currency})") lines.append(f"*Average Price:* {cur_entry_average}") else: sumA = 0 sumB = 0 - for y in range(x): + for y in range(order_nr): amount = filled_orders[y]["filled"] or filled_orders[y]["amount"] sumA += amount * filled_orders[y]["safe_price"] sumB += amount @@ -499,7 +500,7 @@ class Telegram(RPCHandler): if prev_avg_price: minus_on_entry = (cur_entry_average - prev_avg_price) / prev_avg_price - lines.append(f"*{wording} #{x+1}:* at {minus_on_entry:.2%} avg profit") + lines.append(f"*{wording} #{order_nr}:* at {minus_on_entry:.2%} avg profit") if is_open: lines.append("({})".format(cur_entry_datetime .humanize(granularity=["day", "hour", "minute"]))) @@ -518,6 +519,7 @@ class Telegram(RPCHandler): # lines.append( # f"({days}d {hours}h {minutes}m {seconds}s from previous {wording.lower()})") lines_detail.append("\n".join(lines)) + return lines_detail @authorized_only From e482feed7dea7d017c8ffc4023e6a449bf4eb1ea Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 27 Feb 2023 19:40:02 +0100 Subject: [PATCH 104/186] Further improve behavior for telegram /status with stop on exchange --- freqtrade/rpc/telegram.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index d5c76bdb7..de4741f6b 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -487,13 +487,17 @@ class Telegram(RPCHandler): f"*Amount:* {cur_entry_amount} ({order['cost']:.8f} {quote_currency})") lines.append(f"*Average Price:* {cur_entry_average}") else: - sumA = 0 - sumB = 0 + sum_stake = 0 + sum_amount = 0 for y in range(order_nr): - amount = filled_orders[y]["filled"] or filled_orders[y]["amount"] - sumA += amount * filled_orders[y]["safe_price"] - sumB += amount - prev_avg_price = sumA / sumB + loc_order = filled_orders[y] + if loc_order['is_open'] is True: + # Skip open orders (e.g. stop orders) + continue + amount = loc_order["filled"] or loc_order["amount"] + sum_stake += amount * loc_order["safe_price"] + sum_amount += amount + prev_avg_price = sum_stake / sum_amount # TODO: This calculation ignores fees. price_to_1st_entry = ((cur_entry_average - first_avg) / first_avg) minus_on_entry = 0 From e5c68661feb16b9c43daf7bd25e76c3121d668b8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 27 Feb 2023 19:57:28 +0100 Subject: [PATCH 105/186] Simplify code line wrapping --- freqtrade/freqtradebot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 82be6f3b5..7cea4d329 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1275,8 +1275,7 @@ class FreqtradeBot(LoggingMixin): if order['side'] == trade.entry_side: self.handle_cancel_enter(trade, order, reason) else: - canceled = self.handle_cancel_exit( - trade, order, reason) + canceled = self.handle_cancel_exit(trade, order, reason) canceled_count = trade.get_exit_order_count() max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0) if canceled and max_timeouts > 0 and canceled_count >= max_timeouts: From 75d1dd2793a887b38102469b6be1708fbb42e40f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 27 Feb 2023 20:28:55 +0100 Subject: [PATCH 106/186] Properly round Stake currencies in telegram message --- freqtrade/rpc/telegram.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index de4741f6b..cb7d4515f 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -508,8 +508,8 @@ class Telegram(RPCHandler): if is_open: lines.append("({})".format(cur_entry_datetime .humanize(granularity=["day", "hour", "minute"]))) - lines.append( - f"*Amount:* {cur_entry_amount} ({order['cost']:.8f} {quote_currency})") + lines.append(f"*Amount:* {cur_entry_amount} " + f"({round_coin_value(order['cost'], quote_currency)})") lines.append(f"*Average {wording} Price:* {cur_entry_average} " f"({price_to_1st_entry:.2%} from 1st entry rate)") lines.append(f"*Order filled:* {order['order_filled_date']}") @@ -560,13 +560,14 @@ class Telegram(RPCHandler): r['open_date_hum'] = arrow.get(r['open_date']).humanize() r['num_entries'] = len([o for o in r['orders'] if o['ft_is_entry']]) r['exit_reason'] = r.get('exit_reason', "") + r['rounded_stake_amount'] = round_coin_value(r['stake_amount'], r['quote_currency']) lines = [ "*Trade ID:* `{trade_id}`" + (" `(since {open_date_hum})`" if r['is_open'] else ""), "*Current Pair:* {pair}", "*Direction:* " + ("`Short`" if r.get('is_short') else "`Long`"), "*Leverage:* `{leverage}`" if r.get('leverage') else "", - "*Amount:* `{amount} ({stake_amount} {quote_currency})`", + "*Amount:* `{amount} ({rounded_stake_amount})`", "*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "", "*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "", ] From 46b987042b194b53edf71c2b4270babd86669ed5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 27 Feb 2023 20:31:02 +0100 Subject: [PATCH 107/186] Include realized_profit in api output --- freqtrade/rpc/api_server/api_schemas.py | 1 + tests/rpc/test_rpc_apiserver.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 58f6ad583..0831b390f 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -244,6 +244,7 @@ class TradeSchema(BaseModel): profit_pct: Optional[float] profit_abs: Optional[float] profit_fiat: Optional[float] + realized_profit: float exit_reason: Optional[str] exit_order_status: Optional[str] stop_loss_abs: Optional[float] diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 43d9abb78..03e8ca837 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1015,6 +1015,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, 'profit_pct': ANY, 'profit_abs': ANY, 'profit_fiat': ANY, + 'realized_profit': 0.0, 'current_rate': current_rate, 'open_date': ANY, 'open_timestamp': ANY, @@ -1244,6 +1245,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): 'profit_pct': None, 'profit_abs': None, 'profit_fiat': None, + 'realized_profit': 0.0, 'fee_close': 0.0025, 'fee_close_cost': None, 'fee_close_currency': None, From 65d1598a90f4a395f3b1929d3e8887875bdff5ef Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 27 Feb 2023 20:50:25 +0100 Subject: [PATCH 108/186] Show absolute profit in /status command --- freqtrade/rpc/telegram.py | 3 ++- tests/rpc/test_rpc_telegram.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index cb7d4515f..34b54e047 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -561,6 +561,7 @@ class Telegram(RPCHandler): r['num_entries'] = len([o for o in r['orders'] if o['ft_is_entry']]) r['exit_reason'] = r.get('exit_reason', "") r['rounded_stake_amount'] = round_coin_value(r['stake_amount'], r['quote_currency']) + r['rounded_profit_abs'] = round_coin_value(r['profit_abs'], r['quote_currency']) lines = [ "*Trade ID:* `{trade_id}`" + (" `(since {open_date_hum})`" if r['is_open'] else ""), @@ -583,7 +584,7 @@ class Telegram(RPCHandler): "*Close Date:* `{close_date}`" if r['close_date'] else "", "*Current Rate:* `{current_rate:.8f}`" if r['is_open'] else "", ("*Current Profit:* " if r['is_open'] else "*Close Profit: *") - + "`{profit_ratio:.2%}`", + + "`{profit_ratio:.2%}` `({rounded_profit_abs})`", ]) if r['is_open']: diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 855062af0..974cc45f7 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -202,6 +202,7 @@ def test_telegram_status(default_conf, update, mocker) -> None: 'close_profit_ratio': None, 'profit': -0.0059, 'profit_ratio': -0.0059, + 'profit_abs': -0.225, 'initial_stop_loss_abs': 1.098e-05, 'stop_loss_abs': 1.099e-05, 'exit_order_status': None, From 39331b59ed011d40f1d314624ee30f88cda6fdd0 Mon Sep 17 00:00:00 2001 From: Rahul Date: Mon, 27 Feb 2023 22:51:22 +0000 Subject: [PATCH 109/186] Fixed issues raised in PR --- docs/telegram-usage.md | 14 +++++++--- freqtrade/enums/marketstatetype.py | 6 ++++- freqtrade/rpc/rpc.py | 5 +++- freqtrade/rpc/telegram.py | 42 ++++++++++++++++++++---------- 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index a4145df02..dfab3754c 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -179,7 +179,7 @@ official commands. You can ask at any moment for help with `/help`. | `/count` | Displays number of trades used and available | `/locks` | Show currently locked pairs. | `/unlock ` | Remove the lock for this pair (or for this lock id). -| `/marketdir [long | short | even | none]` | Updates the user managed variable that represents the current market direction. +| `/marketdir [long | short | even | none]` | Updates the user managed variable that represents the current market direction. If no direction is provided, the currently set direction will be displayed. | **Modify Trade states** | | `/forceexit | /fx ` | Instantly exits the given trade (Ignoring `minimum_roi`). | `/forceexit all | /fx all` | Instantly exits all open trades (Ignoring `minimum_roi`). @@ -420,6 +420,12 @@ ARDR/ETH 0.366667 0.143059 -0.01 ### /marketdir -Updates the user managed variable that represents the current market direction. This variable is not set -to any valid market direction on bot startup and must be set by the user. As an example `/marketdir long` -would set the variable to be `long`. +If a market direction is provided the command updates the user managed variable that represents the current market direction. +This variable is not set to any valid market direction on bot startup and must be set by the user. The example below is for `/marketdir long`: +``` +Successfully updated marketdirection from none to long. +``` +If no market direction is provided the command outputs the currently set market directions. The example below is for `/marketdir`: +``` +Currently set marketdirection: even +``` diff --git a/freqtrade/enums/marketstatetype.py b/freqtrade/enums/marketstatetype.py index 8132be74a..5cede32c2 100644 --- a/freqtrade/enums/marketstatetype.py +++ b/freqtrade/enums/marketstatetype.py @@ -8,4 +8,8 @@ class MarketDirection(Enum): LONG = "long" SHORT = "short" EVEN = "even" - NONE = '' + NONE = "none" + + def __str__(self): + # convert to string + return self.value diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index f1e6c15e6..d2e66cfff 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -1206,5 +1206,8 @@ class RPC: 'last_process_ts': int(last_p.timestamp()), } - def _update_market_direction(self, direction: MarketDirection): + def _update_market_direction(self, direction: MarketDirection) -> None: self._freqtrade.strategy.market_direction = direction + + def _get_market_direction(self) -> MarketDirection: + return self._freqtrade.strategy.market_direction diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 5f682b436..050dc3f31 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -129,7 +129,8 @@ class Telegram(RPCHandler): r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', r'/forcebuy$', r'/forcelong$', r'/forceshort$', r'/forcesell$', r'/forceexit$', - r'/edge$', r'/health$', r'/help$', r'/version$', r'/marketdir (long|short|even|none)$' + r'/edge$', r'/health$', r'/help$', r'/version$', r'/marketdir (long|short|even|none)$', + r'/marketdir$' ] # Create keys for generation valid_keys_print = [k.replace('$', '') for k in valid_keys] @@ -1495,8 +1496,9 @@ class Telegram(RPCHandler): "*/count:* `Show number of active trades compared to allowed number of trades`\n" "*/edge:* `Shows validated pairs by Edge if it is enabled` \n" "*/health* `Show latest process timestamp - defaults to 1970-01-01 00:00:00` \n" - "*/marketdir [long | short | even | none]:* `Updates the user managed variable" - " that represents the current market direction` \n" + "*/marketdir [long | short | even | none]:* `Updates the user managed variable " + "that represents the current market direction. If no direction is provided `" + "`the currently set market direction will be output.` \n" "_Statistics_\n" "------------\n" @@ -1691,16 +1693,28 @@ class Telegram(RPCHandler): :return: None """ if context.args and len(context.args) == 1: - new_market_dir = context.args[0] - if new_market_dir == "long": - self._rpc._update_market_direction(MarketDirection.LONG) - elif new_market_dir == "short": - self._rpc._update_market_direction(MarketDirection.SHORT) - elif new_market_dir == "even": - self._rpc._update_market_direction(MarketDirection.EVEN) - elif new_market_dir == "none": - self._rpc._update_market_direction(MarketDirection.NONE) + new_market_dir_arg = context.args[0] + old_market_dir = self._rpc._get_market_direction() + new_market_dir = None + if new_market_dir_arg == "long": + new_market_dir = MarketDirection.LONG + elif new_market_dir_arg == "short": + new_market_dir = MarketDirection.SHORT + elif new_market_dir_arg == "even": + new_market_dir = MarketDirection.EVEN + elif new_market_dir_arg == "none": + new_market_dir = MarketDirection.NONE + + if new_market_dir is not None: + self._rpc._update_market_direction(new_market_dir) + self._send_msg("Successfully updated market direction" + f" from *{old_market_dir}* to *{new_market_dir}*.") else: - raise RPCException("Invalid market direction provided") + raise RPCException("Invalid market direction provided. \n" + "Valid market directions: *long, short, even, none*") + elif context.args is not None and len(context.args) == 0: + old_market_dir = self._rpc._get_market_direction() + self._send_msg(f"Currently set market direction: *{old_market_dir}*") else: - raise RPCException("Invalid usage of command /marketdir.") + raise RPCException("Invalid usage of command /marketdir. \n" + "Usage: */marketdir [short | long | even | none]*") From 0899e5cb8337adde4de58e0b80a578c4af0ed497 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 06:41:18 +0100 Subject: [PATCH 110/186] Improve documentation wording --- docs/telegram-usage.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index dfab3754c..653d31ee6 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -422,10 +422,18 @@ ARDR/ETH 0.366667 0.143059 -0.01 If a market direction is provided the command updates the user managed variable that represents the current market direction. This variable is not set to any valid market direction on bot startup and must be set by the user. The example below is for `/marketdir long`: + ``` Successfully updated marketdirection from none to long. ``` + If no market direction is provided the command outputs the currently set market directions. The example below is for `/marketdir`: + ``` Currently set marketdirection: even ``` + +You can use the market direction in your strategy via `self.market_direction`. + +!!! Warning "Bot restarts" + Please note that the market direction is not persisted, and will be reset after a bot restart/reload. From a75e9f193f134a400868d2e74bd64fabb50214f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 05:52:21 +0000 Subject: [PATCH 111/186] Bump ruff from 0.0.252 to 0.0.253 Bumps [ruff](https://github.com/charliermarsh/ruff) from 0.0.252 to 0.0.253. - [Release notes](https://github.com/charliermarsh/ruff/releases) - [Changelog](https://github.com/charliermarsh/ruff/blob/main/BREAKING_CHANGES.md) - [Commits](https://github.com/charliermarsh/ruff/compare/v0.0.252...v0.0.253) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f45daaabd..6754591a6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==3.3.1 -ruff==0.0.252 +ruff==0.0.253 mypy==1.0.1 pre-commit==3.1.0 pytest==7.2.1 From 6e45e998ac657c76f0ea399d01d664e8828dc3f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 05:52:29 +0000 Subject: [PATCH 112/186] Bump pre-commit from 3.1.0 to 3.1.1 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.1.0 to 3.1.1. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v3.1.0...v3.1.1) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f45daaabd..a187ceedc 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,7 +9,7 @@ coveralls==3.3.1 ruff==0.0.252 mypy==1.0.1 -pre-commit==3.1.0 +pre-commit==3.1.1 pytest==7.2.1 pytest-asyncio==0.20.3 pytest-cov==4.0.0 From 78e7ab92d8f833876c880e75667301884239fc78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 05:52:35 +0000 Subject: [PATCH 113/186] Bump plotly from 5.13.0 to 5.13.1 Bumps [plotly](https://github.com/plotly/plotly.py) from 5.13.0 to 5.13.1. - [Release notes](https://github.com/plotly/plotly.py/releases) - [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md) - [Commits](https://github.com/plotly/plotly.py/compare/v5.13.0...v5.13.1) --- updated-dependencies: - dependency-name: plotly dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index b97d42fb6..ad7bade95 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,4 +1,4 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==5.13.0 +plotly==5.13.1 From 1b4c831469b2d4a72ec0e29ea94ef7c2f10a1ffb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 05:52:40 +0000 Subject: [PATCH 114/186] Bump prompt-toolkit from 3.0.36 to 3.0.37 Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.36 to 3.0.37. - [Release notes](https://github.com/prompt-toolkit/python-prompt-toolkit/releases) - [Changelog](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/CHANGELOG) - [Commits](https://github.com/prompt-toolkit/python-prompt-toolkit/compare/3.0.36...3.0.37) --- updated-dependencies: - dependency-name: prompt-toolkit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index da990721f..903fc90a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -45,7 +45,7 @@ psutil==5.9.4 colorama==0.4.6 # Building config files interactively questionary==1.10.0 -prompt-toolkit==3.0.36 +prompt-toolkit==3.0.37 # Extensions to datetime library python-dateutil==2.8.2 From adf5b7f233db199dca4014a5ded6a9900681be9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 05:52:48 +0000 Subject: [PATCH 115/186] Bump ccxt from 2.8.17 to 2.8.54 Bumps [ccxt](https://github.com/ccxt/ccxt) from 2.8.17 to 2.8.54. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/2.8.17...2.8.54) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index da990721f..9ed7e8943 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.24.2 pandas==1.5.3 pandas-ta==0.3.14b -ccxt==2.8.17 +ccxt==2.8.54 cryptography==39.0.1 aiohttp==3.8.4 SQLAlchemy==1.4.46 From fed5d87cfd72ddd2d54f553e63f17d3a10ad7247 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 05:52:55 +0000 Subject: [PATCH 116/186] Bump xgboost from 1.7.3 to 1.7.4 Bumps [xgboost](https://github.com/dmlc/xgboost) from 1.7.3 to 1.7.4. - [Release notes](https://github.com/dmlc/xgboost/releases) - [Changelog](https://github.com/dmlc/xgboost/blob/master/NEWS.md) - [Commits](https://github.com/dmlc/xgboost/compare/v1.7.3...v1.7.4) --- updated-dependencies: - dependency-name: xgboost dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-freqai.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index cf5bc4c0b..5b27ecf95 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -7,5 +7,5 @@ scikit-learn==1.1.3 joblib==1.2.0 catboost==1.1.1; platform_machine != 'aarch64' lightgbm==3.3.5 -xgboost==1.7.3 +xgboost==1.7.4 tensorboard==2.12.0 From 594757d27dad985067380e6aec72f3fbdec65bed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 05:52:58 +0000 Subject: [PATCH 117/186] Bump types-python-dateutil from 2.8.19.8 to 2.8.19.9 Bumps [types-python-dateutil](https://github.com/python/typeshed) from 2.8.19.8 to 2.8.19.9. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-python-dateutil dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f45daaabd..711ac68f3 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -29,4 +29,4 @@ types-cachetools==5.3.0.0 types-filelock==3.2.7 types-requests==2.28.11.15 types-tabulate==0.9.0.1 -types-python-dateutil==2.8.19.8 +types-python-dateutil==2.8.19.9 From deca5479f0ddd16d2c2ee16f338fd14c2d2af46f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 10:05:38 +0100 Subject: [PATCH 118/186] pre-commit dateutil-types --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8711a65dc..a0ff2e6d5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - types-filelock==3.2.7 - types-requests==2.28.11.15 - types-tabulate==0.9.0.1 - - types-python-dateutil==2.8.19.8 + - types-python-dateutil==2.8.19.9 # stages: [push] - repo: https://github.com/pycqa/isort From 5a3f23f00caede88cb9b1ede2245a1d8bd6a5654 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 09:26:32 +0000 Subject: [PATCH 119/186] Bump types-cachetools from 5.3.0.0 to 5.3.0.4 Bumps [types-cachetools](https://github.com/python/typeshed) from 5.3.0.0 to 5.3.0.4. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-cachetools dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 3fee2dc65..2ba004f8d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -25,7 +25,7 @@ httpx==0.23.3 nbconvert==7.2.9 # mypy types -types-cachetools==5.3.0.0 +types-cachetools==5.3.0.4 types-filelock==3.2.7 types-requests==2.28.11.15 types-tabulate==0.9.0.1 From 9a5b0908940d58af2cfeb6a6b913c948a5ff95c0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 11:23:11 +0100 Subject: [PATCH 120/186] pre-commit cachetools --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a0ff2e6d5..05f4df92b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: mypy exclude: build_helpers additional_dependencies: - - types-cachetools==5.3.0.0 + - types-cachetools==5.3.0.4 - types-filelock==3.2.7 - types-requests==2.28.11.15 - types-tabulate==0.9.0.1 From 262f03bc928abed50ab8d26c8ab886a11fde8bf9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 17:25:42 +0100 Subject: [PATCH 121/186] Add backtest warning for market_direction feature --- docs/telegram-usage.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 653d31ee6..1b7502184 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -437,3 +437,7 @@ You can use the market direction in your strategy via `self.market_direction`. !!! Warning "Bot restarts" Please note that the market direction is not persisted, and will be reset after a bot restart/reload. + +!!! Danger "Backtesting" + As this value/variable is intended to be changed manually in dry/live trading. + Strategies using `market_direction` will probably not produce reliable, reproducible results (changes to this variable will not be reflected for backtesting). Use at your own risk. From bebee15d100257f19f86532e718e9a2d4353cf1a Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 18:09:52 +0100 Subject: [PATCH 122/186] Improve TradeSchema readability --- freqtrade/rpc/api_server/api_schemas.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index e5b038d90..af64019d5 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -228,25 +228,32 @@ class TradeSchema(BaseModel): fee_close: Optional[float] fee_close_cost: Optional[float] fee_close_currency: Optional[str] + open_date: str open_timestamp: int open_rate: float open_rate_requested: Optional[float] open_trade_value: float + close_date: Optional[str] close_timestamp: Optional[int] close_rate: Optional[float] close_rate_requested: Optional[float] + close_profit: Optional[float] close_profit_pct: Optional[float] close_profit_abs: Optional[float] + profit_ratio: Optional[float] profit_pct: Optional[float] profit_abs: Optional[float] profit_fiat: Optional[float] + realized_profit: float + exit_reason: Optional[str] exit_order_status: Optional[str] + stop_loss_abs: Optional[float] stop_loss_ratio: Optional[float] stop_loss_pct: Optional[float] @@ -256,6 +263,7 @@ class TradeSchema(BaseModel): initial_stop_loss_abs: Optional[float] initial_stop_loss_ratio: Optional[float] initial_stop_loss_pct: Optional[float] + min_rate: Optional[float] max_rate: Optional[float] open_order_id: Optional[str] From 0707e70183e8f534f5683ce827eab8067cc0d831 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 18:15:58 +0100 Subject: [PATCH 123/186] Remove deprecated current_profit from api responses --- freqtrade/rpc/api_server/api_schemas.py | 3 --- freqtrade/rpc/rpc.py | 3 --- tests/rpc/test_rpc.py | 11 +---------- tests/rpc/test_rpc_apiserver.py | 3 --- 4 files changed, 1 insertion(+), 19 deletions(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index af64019d5..bb30678ab 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -282,9 +282,6 @@ class OpenTradeSchema(TradeSchema): stoploss_current_dist_ratio: Optional[float] stoploss_entry_dist: Optional[float] stoploss_entry_dist_ratio: Optional[float] - current_profit: float - current_profit_abs: float - current_profit_pct: float current_rate: float open_order: Optional[str] diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index ba05d3d7f..807efb4fc 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -210,9 +210,6 @@ class RPC: trade_dict.update(dict( close_profit=trade.close_profit if not trade.is_open else None, current_rate=current_rate, - current_profit=current_profit, # Deprecated - current_profit_pct=round(current_profit * 100, 2), # Deprecated - current_profit_abs=current_profit_abs, # Deprecated profit_ratio=current_profit, profit_pct=round(current_profit * 100, 2), profit_abs=current_profit_abs, diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index d9b7c764a..e179ec9da 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -68,9 +68,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'close_profit': None, 'close_profit_pct': None, 'close_profit_abs': None, - 'current_profit': -0.00408133, - 'current_profit_pct': -0.41, - 'current_profit_abs': -4.09e-06, 'profit_ratio': -0.00408133, 'profit_pct': -0.41, 'profit_abs': -4.09e-06, @@ -134,9 +131,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'profit_ratio': 0.0, 'profit_pct': 0.0, 'profit_abs': 0.0, - 'current_profit': 0.0, - 'current_profit_pct': 0.0, - 'current_profit_abs': 0.0, 'stop_loss_abs': 0.0, 'stop_loss_pct': None, 'stop_loss_ratio': None, @@ -187,7 +181,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))) results = rpc._rpc_trade_status() - assert isnan(results[0]['current_profit']) + assert isnan(results[0]['profit_ratio']) assert isnan(results[0]['current_rate']) response_norate = deepcopy(gen_response) # Update elements that are NaN when no rate is available. @@ -198,9 +192,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'profit_ratio': ANY, 'profit_pct': ANY, 'profit_abs': ANY, - 'current_profit_abs': ANY, - 'current_profit': ANY, - 'current_profit_pct': ANY, 'current_rate': ANY, }) assert results[0] == response_norate diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 8892995c7..3697fac05 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1008,9 +1008,6 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, 'close_profit_pct': None, 'close_profit_abs': None, 'close_rate': None, - 'current_profit': ANY, - 'current_profit_pct': ANY, - 'current_profit_abs': ANY, 'profit_ratio': ANY, 'profit_pct': ANY, 'profit_abs': ANY, From 3706d28125bff39e8cdf48b07f46dff92ad52913 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 18:17:29 +0100 Subject: [PATCH 124/186] use pytest.approx in favor of "prec_satoshi" ... --- tests/rpc/test_rpc.py | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index e179ec9da..d2ea1ffde 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -19,15 +19,6 @@ from tests.conftest import (create_mock_trades, create_mock_trades_usdt, get_pat patch_get_signal) -# Functions for recurrent object patching -def prec_satoshi(a, b) -> float: - """ - :return: True if A and B differs less than one satoshi. - """ - return abs(a - b) < 0.00000001 - - -# Unit tests def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: gen_response = { 'trade_id': 1, @@ -544,8 +535,8 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): rpc._fiat_converter = CryptoToFiatConverter() result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency']) - assert prec_satoshi(result['total'], 30.30909624) - assert prec_satoshi(result['value'], 454636.44360691) + assert pytest.approx(result['total']) == 30.30909624 + assert pytest.approx(result['value']) == 454636.44360691 assert tickers.call_count == 1 assert tickers.call_args_list[0][1]['cached'] is True assert 'USD' == result['symbol'] @@ -866,17 +857,17 @@ def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee): assert len(res) == 2 assert res[0]['enter_tag'] == 'TEST1' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit_pct'], 0.5) + assert pytest.approx(res[0]['profit_pct']) == 0.5 assert res[1]['enter_tag'] == 'Other' assert res[1]['count'] == 1 - assert prec_satoshi(res[1]['profit_pct'], 1.0) + assert pytest.approx(res[1]['profit_pct']) == 1.0 # Test for a specific pair res = rpc._rpc_enter_tag_performance('ETC/BTC') assert len(res) == 1 assert res[0]['count'] == 1 assert res[0]['enter_tag'] == 'TEST1' - assert prec_satoshi(res[0]['profit_pct'], 0.5) + assert pytest.approx(res[0]['profit_pct']) == 0.5 def test_exit_reason_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None: @@ -922,17 +913,17 @@ def test_exit_reason_performance_handle_2(mocker, default_conf, markets, fee): assert len(res) == 2 assert res[0]['exit_reason'] == 'sell_signal' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit_pct'], 0.5) + assert pytest.approx(res[0]['profit_pct']) == 0.5 assert res[1]['exit_reason'] == 'roi' assert res[1]['count'] == 1 - assert prec_satoshi(res[1]['profit_pct'], 1.0) + assert pytest.approx(res[1]['profit_pct']) == 1.0 # Test for a specific pair res = rpc._rpc_exit_reason_performance('ETC/BTC') assert len(res) == 1 assert res[0]['count'] == 1 assert res[0]['exit_reason'] == 'sell_signal' - assert prec_satoshi(res[0]['profit_pct'], 0.5) + assert pytest.approx(res[0]['profit_pct']) == 0.5 def test_mix_tag_performance_handle(default_conf, ticker, fee, mocker) -> None: @@ -975,10 +966,10 @@ def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee): assert len(res) == 2 assert res[0]['mix_tag'] == 'TEST1 sell_signal' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit_pct'], 0.5) + assert pytest.approx(res[0]['profit_pct']) == 0.5 assert res[1]['mix_tag'] == 'Other roi' assert res[1]['count'] == 1 - assert prec_satoshi(res[1]['profit_pct'], 1.0) + assert pytest.approx(res[1]['profit_pct']) == 1.0 # Test for a specific pair res = rpc._rpc_mix_tag_performance('ETC/BTC') @@ -986,7 +977,7 @@ def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee): assert len(res) == 1 assert res[0]['count'] == 1 assert res[0]['mix_tag'] == 'TEST1 sell_signal' - assert prec_satoshi(res[0]['profit_pct'], 0.5) + assert pytest.approx(res[0]['profit_pct']) == 0.5 def test_rpc_count(mocker, default_conf, ticker, fee) -> None: From 2f1c5cf1439ba6f562dc46d33ec8ab169572344b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 18:22:17 +0100 Subject: [PATCH 125/186] Remove pointless pylint rules --- tests/rpc/test_rpc.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index d2ea1ffde..8e3eabd66 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -1,6 +1,3 @@ -# pragma pylint: disable=missing-docstring, C0103 -# pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments - from copy import deepcopy from datetime import datetime, timedelta, timezone from unittest.mock import ANY, MagicMock, PropertyMock @@ -535,7 +532,7 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): rpc._fiat_converter = CryptoToFiatConverter() result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency']) - assert pytest.approx(result['total']) == 30.30909624 + assert pytest.approx(result['total']) == 30.30909624 assert pytest.approx(result['value']) == 454636.44360691 assert tickers.call_count == 1 assert tickers.call_args_list[0][1]['cached'] is True From 386915378b11e14d1aad49bc834700eda1c5cb6c Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 19:54:47 +0100 Subject: [PATCH 126/186] Improve /status message (show Total profit) --- docs/telegram-usage.md | 2 +- freqtrade/rpc/rpc.py | 9 +++++++++ freqtrade/rpc/telegram.py | 17 +++++++++++------ tests/rpc/test_rpc.py | 4 ++++ tests/rpc/test_rpc_telegram.py | 2 ++ 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 1b7502184..dc0ab0976 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -243,7 +243,7 @@ Enter Tag is configurable via Strategy. > **Enter Tag:** Awesome Long Signal > **Open Rate:** `0.00007489` > **Current Rate:** `0.00007489` -> **Current Profit:** `12.95%` +> **Unrealized Profit:** `12.95%` > **Stoploss:** `0.00007389 (-0.02%)` ### /status table diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 807efb4fc..230079cad 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -169,6 +169,7 @@ class RPC: for trade in trades: order: Optional[Order] = None current_profit_fiat: Optional[float] = None + combined_profit_fiat: Optional[float] = None if trade.open_order_id: order = trade.select_order_by_order_id(trade.open_order_id) # calculate profit and send message to user @@ -190,6 +191,7 @@ class RPC: current_rate = trade.close_rate current_profit = trade.close_profit current_profit_abs = trade.close_profit_abs + combined_profit_abs = trade.realized_profit + current_profit_abs # Calculate fiat profit if not isnan(current_profit_abs) and self._fiat_converter: @@ -198,6 +200,11 @@ class RPC: self._freqtrade.config['stake_currency'], self._freqtrade.config['fiat_display_currency'] ) + combined_profit_fiat = self._fiat_converter.convert_amount( + combined_profit_abs, + self._freqtrade.config['stake_currency'], + self._freqtrade.config['fiat_display_currency'] + ) # Calculate guaranteed profit (in case of trailing stop) stoploss_entry_dist = trade.calc_profit(trade.stop_loss) @@ -215,6 +222,8 @@ class RPC: profit_abs=current_profit_abs, profit_fiat=current_profit_fiat, + combined_profit_abs=combined_profit_abs, + combined_profit_fiat=combined_profit_fiat, stoploss_current_dist=stoploss_current_dist, stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8), stoploss_current_dist_pct=round(stoploss_current_dist_ratio * 100, 2), diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index b961e6fd7..e7bc2a6d7 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -562,15 +562,18 @@ class Telegram(RPCHandler): r['open_date_hum'] = arrow.get(r['open_date']).humanize() r['num_entries'] = len([o for o in r['orders'] if o['ft_is_entry']]) r['exit_reason'] = r.get('exit_reason', "") - r['rounded_stake_amount'] = round_coin_value(r['stake_amount'], r['quote_currency']) - r['rounded_profit_abs'] = round_coin_value(r['profit_abs'], r['quote_currency']) + r['stake_amount_r'] = round_coin_value(r['stake_amount'], r['quote_currency']) + r['profit_abs_r'] = round_coin_value(r['profit_abs'], r['quote_currency']) + r['realized_profit_r'] = round_coin_value(r['realized_profit'], r['quote_currency']) + r['combined_profit_abs_r'] = round_coin_value( + r['combined_profit_abs'], r['quote_currency']) lines = [ "*Trade ID:* `{trade_id}`" + (" `(since {open_date_hum})`" if r['is_open'] else ""), "*Current Pair:* {pair}", "*Direction:* " + ("`Short`" if r.get('is_short') else "`Long`"), "*Leverage:* `{leverage}`" if r.get('leverage') else "", - "*Amount:* `{amount} ({rounded_stake_amount})`", + "*Amount:* `{amount} ({stake_amount_r})`", "*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "", "*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "", ] @@ -585,13 +588,15 @@ class Telegram(RPCHandler): "*Open Date:* `{open_date}`", "*Close Date:* `{close_date}`" if r['close_date'] else "", "*Current Rate:* `{current_rate:.8f}`" if r['is_open'] else "", - ("*Current Profit:* " if r['is_open'] else "*Close Profit: *") - + "`{profit_ratio:.2%}` `({rounded_profit_abs})`", + ("*Unrealized Profit:* " if r['is_open'] else "*Close Profit: *") + + "`{profit_ratio:.2%}` `({profit_abs_r})`", ]) if r['is_open']: if r.get('realized_profit'): - lines.append("*Realized Profit:* `{realized_profit:.8f}`") + lines.append("*Realized Profit:* `{realized_profit_r}`") + lines.append("*Total Profit:* `{combined_profit_abs_r}` ") + if (r['stop_loss_abs'] != r['initial_stop_loss_abs'] and r['initial_stop_loss_ratio'] is not None): # Adding initial stoploss only if it is different from stoploss diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 8e3eabd66..734eb7cf1 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -76,6 +76,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stoploss_entry_dist_ratio': -0.10376381, 'open_order': None, 'realized_profit': 0.0, + 'combined_profit_abs': -4.09e-06, + 'combined_profit_fiat': ANY, 'exchange': 'binance', 'leverage': 1.0, 'interest_rate': 0.0, @@ -119,6 +121,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'profit_ratio': 0.0, 'profit_pct': 0.0, 'profit_abs': 0.0, + 'combined_profit_abs': 0.0, 'stop_loss_abs': 0.0, 'stop_loss_pct': None, 'stop_loss_ratio': None, @@ -180,6 +183,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'profit_ratio': ANY, 'profit_pct': ANY, 'profit_abs': ANY, + 'combined_profit_abs': ANY, 'current_rate': ANY, }) assert results[0] == response_norate diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index dd58c53a3..2a60f0b42 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -204,6 +204,8 @@ def test_telegram_status(default_conf, update, mocker) -> None: 'profit': -0.0059, 'profit_ratio': -0.0059, 'profit_abs': -0.225, + 'realized_profit': 0.0, + 'combined_profit_abs': -0.225, 'initial_stop_loss_abs': 1.098e-05, 'stop_loss_abs': 1.099e-05, 'exit_order_status': None, From f822f1795a7f98faa9c29b118f122700320dbe53 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 19:54:56 +0100 Subject: [PATCH 127/186] Reduce `/status` verbosity --- freqtrade/rpc/telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e7bc2a6d7..18e412bbb 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -571,8 +571,8 @@ class Telegram(RPCHandler): "*Trade ID:* `{trade_id}`" + (" `(since {open_date_hum})`" if r['is_open'] else ""), "*Current Pair:* {pair}", - "*Direction:* " + ("`Short`" if r.get('is_short') else "`Long`"), - "*Leverage:* `{leverage}`" if r.get('leverage') else "", + f"*Direction:* {'`Short`' if r.get('is_short') else '`Long`'}" + + " ` ({leverage}x)`" if r.get('leverage') else "", "*Amount:* `{amount} ({stake_amount_r})`", "*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "", "*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "", From dd10dec73d6f025b40bee247ab5f5aa9901720d4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 20:31:02 +0100 Subject: [PATCH 128/186] Improve variable wording --- freqtrade/rpc/api_server/api_schemas.py | 3 +++ freqtrade/rpc/rpc.py | 12 ++++++------ freqtrade/rpc/telegram.py | 6 +++--- tests/rpc/test_rpc.py | 8 ++++---- tests/rpc/test_rpc_apiserver.py | 2 ++ tests/rpc/test_rpc_telegram.py | 2 +- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index bb30678ab..562c9aa7d 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -283,6 +283,9 @@ class OpenTradeSchema(TradeSchema): stoploss_entry_dist: Optional[float] stoploss_entry_dist_ratio: Optional[float] current_rate: float + total_profit_abs: float + total_profit_fiat: Optional[float] + open_order: Optional[str] diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 230079cad..82f892101 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -169,7 +169,7 @@ class RPC: for trade in trades: order: Optional[Order] = None current_profit_fiat: Optional[float] = None - combined_profit_fiat: Optional[float] = None + total_profit_fiat: Optional[float] = None if trade.open_order_id: order = trade.select_order_by_order_id(trade.open_order_id) # calculate profit and send message to user @@ -191,7 +191,7 @@ class RPC: current_rate = trade.close_rate current_profit = trade.close_profit current_profit_abs = trade.close_profit_abs - combined_profit_abs = trade.realized_profit + current_profit_abs + total_profit_abs = trade.realized_profit + current_profit_abs # Calculate fiat profit if not isnan(current_profit_abs) and self._fiat_converter: @@ -200,8 +200,8 @@ class RPC: self._freqtrade.config['stake_currency'], self._freqtrade.config['fiat_display_currency'] ) - combined_profit_fiat = self._fiat_converter.convert_amount( - combined_profit_abs, + total_profit_fiat = self._fiat_converter.convert_amount( + total_profit_abs, self._freqtrade.config['stake_currency'], self._freqtrade.config['fiat_display_currency'] ) @@ -222,8 +222,8 @@ class RPC: profit_abs=current_profit_abs, profit_fiat=current_profit_fiat, - combined_profit_abs=combined_profit_abs, - combined_profit_fiat=combined_profit_fiat, + total_profit_abs=total_profit_abs, + total_profit_fiat=total_profit_fiat, stoploss_current_dist=stoploss_current_dist, stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8), stoploss_current_dist_pct=round(stoploss_current_dist_ratio * 100, 2), diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 18e412bbb..7bbeea2a2 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -565,8 +565,8 @@ class Telegram(RPCHandler): r['stake_amount_r'] = round_coin_value(r['stake_amount'], r['quote_currency']) r['profit_abs_r'] = round_coin_value(r['profit_abs'], r['quote_currency']) r['realized_profit_r'] = round_coin_value(r['realized_profit'], r['quote_currency']) - r['combined_profit_abs_r'] = round_coin_value( - r['combined_profit_abs'], r['quote_currency']) + r['total_profit_abs_r'] = round_coin_value( + r['total_profit_abs'], r['quote_currency']) lines = [ "*Trade ID:* `{trade_id}`" + (" `(since {open_date_hum})`" if r['is_open'] else ""), @@ -595,7 +595,7 @@ class Telegram(RPCHandler): if r['is_open']: if r.get('realized_profit'): lines.append("*Realized Profit:* `{realized_profit_r}`") - lines.append("*Total Profit:* `{combined_profit_abs_r}` ") + lines.append("*Total Profit:* `{total_profit_abs_r}` ") if (r['stop_loss_abs'] != r['initial_stop_loss_abs'] and r['initial_stop_loss_ratio'] is not None): diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 734eb7cf1..3eb391edd 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -76,8 +76,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stoploss_entry_dist_ratio': -0.10376381, 'open_order': None, 'realized_profit': 0.0, - 'combined_profit_abs': -4.09e-06, - 'combined_profit_fiat': ANY, + 'total_profit_abs': -4.09e-06, + 'total_profit_fiat': ANY, 'exchange': 'binance', 'leverage': 1.0, 'interest_rate': 0.0, @@ -121,7 +121,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'profit_ratio': 0.0, 'profit_pct': 0.0, 'profit_abs': 0.0, - 'combined_profit_abs': 0.0, + 'total_profit_abs': 0.0, 'stop_loss_abs': 0.0, 'stop_loss_pct': None, 'stop_loss_ratio': None, @@ -183,7 +183,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'profit_ratio': ANY, 'profit_pct': ANY, 'profit_abs': ANY, - 'combined_profit_abs': ANY, + 'total_profit_abs': ANY, 'current_rate': ANY, }) assert results[0] == response_norate diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 3697fac05..67156da45 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1012,6 +1012,8 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, 'profit_pct': ANY, 'profit_abs': ANY, 'profit_fiat': ANY, + 'total_profit_abs': ANY, + 'total_profit_fiat': ANY, 'realized_profit': 0.0, 'current_rate': current_rate, 'open_date': ANY, diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 2a60f0b42..65e676860 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -205,7 +205,7 @@ def test_telegram_status(default_conf, update, mocker) -> None: 'profit_ratio': -0.0059, 'profit_abs': -0.225, 'realized_profit': 0.0, - 'combined_profit_abs': -0.225, + 'total_profit_abs': -0.225, 'initial_stop_loss_abs': 1.098e-05, 'stop_loss_abs': 1.099e-05, 'exit_order_status': None, From d1b2e38ae971800c9e73cb9e4f0eb35758108d0f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 20:39:17 +0100 Subject: [PATCH 129/186] if a stoploss order exists, always allow canceling that --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 94bfc1b72..633e9dc71 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -841,7 +841,7 @@ class FreqtradeBot(LoggingMixin): def cancel_stoploss_on_exchange(self, trade: Trade) -> Trade: # First cancelling stoploss on exchange ... - if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id: + if trade.stoploss_order_id: try: logger.info(f"Canceling stoploss on exchange for {trade}") co = self.exchange.cancel_stoploss_order_with_result( From 8b51f5f5638eecf5f7d6f847767537335f0def06 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 15:12:29 +0100 Subject: [PATCH 130/186] Lowercase exchange ID --- tests/exchange/test_exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 7ccd32155..bdffa0d29 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2341,7 +2341,7 @@ async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog): "kucoin GET https://openapi-v2.kucoin.com/api/v1/market/candles?" "symbol=ETH-BTC&type=5min&startAt=1640268735&endAt=1640418735" "429 Too Many Requests" '{"code":"429000","msg":"Too Many Requests"}')) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="KuCoin") + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kucoin") mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='KuCoin')) msg = "Kucoin 429 error, avoid triggering DDosProtection backoff delay" From 78e5ec13bb17696433549754333006b845cbb297 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 14:21:44 +0100 Subject: [PATCH 131/186] Use absolute path for generic mocks --- tests/conftest.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c74b1f0f1..c0a3661a0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -145,22 +145,23 @@ def patch_exchange( mock_markets=True, mock_supported_modes=True ) -> None: - mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_config', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id)) - mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title())) - mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) + mocker.patch('freqtrade.exchange.exchange.Exchange._load_async_markets', + MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.exchange.Exchange.validate_config', MagicMock()) + mocker.patch('freqtrade.exchange.exchange.Exchange.validate_timeframes', MagicMock()) + mocker.patch('freqtrade.exchange.exchange.Exchange.id', PropertyMock(return_value=id)) + mocker.patch('freqtrade.exchange.exchange.Exchange.name', PropertyMock(return_value=id.title())) + mocker.patch('freqtrade.exchange.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) if mock_markets: if isinstance(mock_markets, bool): mock_markets = get_markets() - mocker.patch('freqtrade.exchange.Exchange.markets', + mocker.patch('freqtrade.exchange.exchange.Exchange.markets', PropertyMock(return_value=mock_markets)) if mock_supported_modes: mocker.patch( - f'freqtrade.exchange.{id.capitalize()}._supported_trading_mode_margin_pairs', + f'freqtrade.exchange.{id}.{id.capitalize()}._supported_trading_mode_margin_pairs', PropertyMock(return_value=[ (TradingMode.MARGIN, MarginMode.CROSS), (TradingMode.MARGIN, MarginMode.ISOLATED), @@ -170,10 +171,11 @@ def patch_exchange( ) if api_mock: - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch('freqtrade.exchange.exchange.Exchange._init_ccxt', + MagicMock(return_value=api_mock)) else: - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.timeframes', PropertyMock( + mocker.patch('freqtrade.exchange.exchange.Exchange._init_ccxt', MagicMock()) + mocker.patch('freqtrade.exchange.exchange.Exchange.timeframes', PropertyMock( return_value=['5m', '15m', '1h', '1d'])) From bcdf4e0fe837621d2d50ca4c7ecb7bc18c054427 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 20:26:04 +0100 Subject: [PATCH 132/186] Use variable for exchange mocks to shorten lines --- tests/commands/test_commands.py | 40 +- tests/conftest.py | 24 +- tests/data/test_dataprovider.py | 8 +- tests/data/test_history.py | 35 +- tests/edge/test_edge.py | 12 +- tests/exchange/test_binance.py | 12 +- tests/exchange/test_bitpanda.py | 4 +- tests/exchange/test_ccxt_compat.py | 9 +- tests/exchange/test_exchange.py | 436 ++++++++---------- tests/exchange/test_gate.py | 18 +- tests/exchange/test_huobi.py | 10 +- tests/exchange/test_kraken.py | 18 +- tests/exchange/test_kucoin.py | 10 +- tests/freqai/test_freqai_interface.py | 4 +- tests/optimize/test_backtest_detail.py | 8 +- tests/optimize/test_backtesting.py | 122 ++--- .../test_backtesting_adjust_position.py | 16 +- tests/optimize/test_edge_cli.py | 6 +- tests/optimize/test_hyperopt.py | 20 +- tests/plugins/test_pairlist.py | 44 +- tests/rpc/test_rpc.py | 35 +- tests/rpc/test_rpc_apiserver.py | 18 +- tests/rpc/test_rpc_telegram.py | 26 +- tests/test_freqtradebot.py | 302 ++++++------ tests/test_integration.py | 27 +- tests/test_worker.py | 4 +- 26 files changed, 582 insertions(+), 686 deletions(-) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 55ffaccb0..d2ce287e9 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -24,7 +24,7 @@ from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException from freqtrade.persistence.models import init_db from freqtrade.persistence.pairlock_middleware import PairLocks -from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_args, log_has, +from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades, get_args, log_has, log_has_re, patch_exchange, patched_configuration_load_config_file) from tests.conftest_trades import MOCK_TRADE_COUNT @@ -454,7 +454,7 @@ def test_list_markets(mocker, markets_static, capsys): assert re.search(r"^BLK/BTC$", captured.out, re.MULTILINE) assert re.search(r"^LTC/USD$", captured.out, re.MULTILINE) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(side_effect=ValueError)) + mocker.patch(f'{EXMS}.markets', PropertyMock(side_effect=ValueError)) # Test --one-column args = [ "list-markets", @@ -643,9 +643,7 @@ def test_download_data_keyboardInterrupt(mocker, markets): dl_mock = mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', MagicMock(side_effect=KeyboardInterrupt)) patch_exchange(mocker) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) args = [ "download-data", "--exchange", "binance", @@ -664,9 +662,7 @@ def test_download_data_timerange(mocker, markets): dl_mock = mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', MagicMock(return_value=["ETH/BTC", "XRP/BTC"])) patch_exchange(mocker) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) args = [ "download-data", "--exchange", "binance", @@ -715,9 +711,7 @@ def test_download_data_no_markets(mocker, caplog): dl_mock = mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', MagicMock(return_value=["ETH/BTC", "XRP/BTC"])) patch_exchange(mocker, id='binance') - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) args = [ "download-data", "--exchange", "binance", @@ -733,9 +727,7 @@ def test_download_data_no_exchange(mocker, caplog): mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', MagicMock(return_value=["ETH/BTC", "XRP/BTC"])) patch_exchange(mocker) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) args = [ "download-data", ] @@ -751,9 +743,7 @@ def test_download_data_no_pairs(mocker): mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', MagicMock(return_value=["ETH/BTC", "XRP/BTC"])) patch_exchange(mocker) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) args = [ "download-data", "--exchange", @@ -771,9 +761,7 @@ def test_download_data_all_pairs(mocker, markets): dl_mock = mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', MagicMock(return_value=["ETH/BTC", "XRP/BTC"])) patch_exchange(mocker) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) args = [ "download-data", "--exchange", @@ -810,9 +798,7 @@ def test_download_data_trades(mocker, caplog): convert_mock = mocker.patch('freqtrade.commands.data_commands.convert_trades_to_ohlcv', MagicMock(return_value=[])) patch_exchange(mocker) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) args = [ "download-data", "--exchange", "kraken", @@ -843,9 +829,7 @@ def test_download_data_trades(mocker, caplog): def test_download_data_data_invalid(mocker): patch_exchange(mocker, id="kraken") - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) args = [ "download-data", "--exchange", "kraken", @@ -862,9 +846,7 @@ def test_start_convert_trades(mocker, caplog): convert_mock = mocker.patch('freqtrade.commands.data_commands.convert_trades_to_ohlcv', MagicMock(return_value=[])) patch_exchange(mocker) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) args = [ "trades-to-ohlcv", "--exchange", "kraken", diff --git a/tests/conftest.py b/tests/conftest.py index c0a3661a0..3c10de4ec 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -40,6 +40,7 @@ np.seterr(all='raise') CURRENT_TEST_STRATEGY = 'StrategyTestV3' TRADE_SIDES = ('long', 'short') +EXMS = 'freqtrade.exchange.exchange.Exchange' def pytest_addoption(parser): @@ -145,19 +146,17 @@ def patch_exchange( mock_markets=True, mock_supported_modes=True ) -> None: - mocker.patch('freqtrade.exchange.exchange.Exchange._load_async_markets', - MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.exchange.Exchange.validate_config', MagicMock()) - mocker.patch('freqtrade.exchange.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.exchange.Exchange.id', PropertyMock(return_value=id)) - mocker.patch('freqtrade.exchange.exchange.Exchange.name', PropertyMock(return_value=id.title())) - mocker.patch('freqtrade.exchange.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) + mocker.patch(f'{EXMS}._load_async_markets', return_value={}) + mocker.patch(f'{EXMS}.validate_config', MagicMock()) + mocker.patch(f'{EXMS}.validate_timeframes', MagicMock()) + mocker.patch(f'{EXMS}.id', PropertyMock(return_value=id)) + mocker.patch(f'{EXMS}.name', PropertyMock(return_value=id.title())) + mocker.patch(f'{EXMS}.precisionMode', PropertyMock(return_value=2)) if mock_markets: if isinstance(mock_markets, bool): mock_markets = get_markets() - mocker.patch('freqtrade.exchange.exchange.Exchange.markets', - PropertyMock(return_value=mock_markets)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=mock_markets)) if mock_supported_modes: mocker.patch( @@ -171,11 +170,10 @@ def patch_exchange( ) if api_mock: - mocker.patch('freqtrade.exchange.exchange.Exchange._init_ccxt', - MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._init_ccxt', return_value=api_mock) else: - mocker.patch('freqtrade.exchange.exchange.Exchange._init_ccxt', MagicMock()) - mocker.patch('freqtrade.exchange.exchange.Exchange.timeframes', PropertyMock( + mocker.patch(f'{EXMS}._init_ccxt', MagicMock()) + mocker.patch(f'{EXMS}.timeframes', PropertyMock( return_value=['5m', '15m', '1h', '1d'])) diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index c6b1dcc5a..ff748e976 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -8,7 +8,7 @@ from freqtrade.data.dataprovider import DataProvider from freqtrade.enums import CandleType, RunMode from freqtrade.exceptions import ExchangeError, OperationalException from freqtrade.plugins.pairlistmanager import PairListManager -from tests.conftest import generate_test_data, get_patched_exchange +from tests.conftest import EXMS, generate_test_data, get_patched_exchange @pytest.mark.parametrize('candle_type', [ @@ -223,7 +223,7 @@ def test_emit_df(mocker, default_conf, ohlcv_history): def test_refresh(mocker, default_conf): refresh_mock = MagicMock() - mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", refresh_mock) + mocker.patch(f"{EXMS}.refresh_latest_ohlcv", refresh_mock) exchange = get_patched_exchange(mocker, default_conf, id="binance") timeframe = default_conf["timeframe"] @@ -281,7 +281,7 @@ def test_market(mocker, default_conf, markets): def test_ticker(mocker, default_conf, tickers): ticker_mock = MagicMock(return_value=tickers()['ETH/BTC']) - mocker.patch("freqtrade.exchange.Exchange.fetch_ticker", ticker_mock) + mocker.patch(f"{EXMS}.fetch_ticker", ticker_mock) exchange = get_patched_exchange(mocker, default_conf) dp = DataProvider(default_conf, exchange) res = dp.ticker('ETH/BTC') @@ -290,7 +290,7 @@ def test_ticker(mocker, default_conf, tickers): assert res['symbol'] == 'ETH/BTC' ticker_mock = MagicMock(side_effect=ExchangeError('Pair not found')) - mocker.patch("freqtrade.exchange.Exchange.fetch_ticker", ticker_mock) + mocker.patch(f"{EXMS}.fetch_ticker", ticker_mock) exchange = get_patched_exchange(mocker, default_conf) dp = DataProvider(default_conf, exchange) res = dp.ticker('UNITTEST/BTC') diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 5cd7327fd..c967f0c89 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -26,7 +26,7 @@ from freqtrade.enums import CandleType from freqtrade.exchange import timeframe_to_minutes from freqtrade.misc import file_dump_json from freqtrade.resolvers import StrategyResolver -from tests.conftest import (CURRENT_TEST_STRATEGY, get_patched_exchange, log_has, log_has_re, +from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_patched_exchange, log_has, log_has_re, patch_exchange) @@ -66,7 +66,7 @@ def test_load_data_7min_timeframe(caplog, testdatadir) -> None: def test_load_data_1min_timeframe(ohlcv_history, mocker, caplog, testdatadir) -> None: - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history) + mocker.patch(f'{EXMS}.get_historic_ohlcv', return_value=ohlcv_history) file = testdatadir / 'UNITTEST_BTC-1m.json' load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC']) assert file.is_file() @@ -77,7 +77,7 @@ def test_load_data_1min_timeframe(ohlcv_history, mocker, caplog, testdatadir) -> def test_load_data_mark(ohlcv_history, mocker, caplog, testdatadir) -> None: - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history) + mocker.patch(f'{EXMS}.get_historic_ohlcv', return_value=ohlcv_history) file = testdatadir / 'futures/UNITTEST_USDT_USDT-1h-mark.json' load_data(datadir=testdatadir, timeframe='1h', pairs=['UNITTEST/BTC'], candle_type='mark') assert file.is_file() @@ -109,7 +109,7 @@ def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog, Test load_pair_history() with 1 min timeframe """ tmpdir1 = Path(tmpdir) - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history_list) + mocker.patch(f'{EXMS}.get_historic_ohlcv', return_value=ohlcv_history_list) exchange = get_patched_exchange(mocker, default_conf) file = tmpdir1 / 'MEME_BTC-1m.json' @@ -277,7 +277,7 @@ def test_download_pair_history( subdir, file_tail ) -> None: - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history_list) + mocker.patch(f'{EXMS}.get_historic_ohlcv', return_value=ohlcv_history_list) exchange = get_patched_exchange(mocker, default_conf) tmpdir1 = Path(tmpdir) file1_1 = tmpdir1 / f'{subdir}MEME_BTC-1m{file_tail}.json' @@ -328,7 +328,7 @@ def test_download_pair_history2(mocker, default_conf, testdatadir) -> None: json_dump_mock = mocker.patch( 'freqtrade.data.history.jsondatahandler.JsonDataHandler.ohlcv_store', return_value=None) - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=tick) + mocker.patch(f'{EXMS}.get_historic_ohlcv', return_value=tick) exchange = get_patched_exchange(mocker, default_conf) _download_pair_history(datadir=testdatadir, exchange=exchange, pair="UNITTEST/BTC", timeframe='1m', candle_type='spot') @@ -340,7 +340,7 @@ def test_download_pair_history2(mocker, default_conf, testdatadir) -> None: def test_download_backtesting_data_exception(mocker, caplog, default_conf, tmpdir) -> None: - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', + mocker.patch(f'{EXMS}.get_historic_ohlcv', side_effect=Exception('File Error')) tmpdir1 = Path(tmpdir) exchange = get_patched_exchange(mocker, default_conf) @@ -506,9 +506,7 @@ def test_refresh_backtest_ohlcv_data( mocker, default_conf, markets, caplog, testdatadir, trademode, callcount): dl_mock = mocker.patch('freqtrade.data.history.history_utils._download_pair_history', MagicMock()) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) mocker.patch.object(Path, "exists", MagicMock(return_value=True)) mocker.patch.object(Path, "unlink", MagicMock()) @@ -531,9 +529,7 @@ def test_download_data_no_markets(mocker, default_conf, caplog, testdatadir): MagicMock()) ex = get_patched_exchange(mocker, default_conf) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) timerange = TimeRange.parse_timerange("20190101-20190102") unav_pairs = refresh_backtest_ohlcv_data(exchange=ex, pairs=["BTT/BTC", "LTC/USDT"], timeframes=["1m", "5m"], @@ -551,9 +547,7 @@ def test_download_data_no_markets(mocker, default_conf, caplog, testdatadir): def test_refresh_backtest_trades_data(mocker, default_conf, markets, caplog, testdatadir): dl_mock = mocker.patch('freqtrade.data.history.history_utils._download_trades_history', MagicMock()) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) mocker.patch.object(Path, "exists", MagicMock(return_value=True)) mocker.patch.object(Path, "unlink", MagicMock()) @@ -577,8 +571,7 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad tmpdir) -> None: tmpdir1 = Path(tmpdir) ght_mock = MagicMock(side_effect=lambda pair, *args, **kwargs: (pair, trades_history)) - mocker.patch('freqtrade.exchange.Exchange.get_historic_trades', - ght_mock) + mocker.patch(f'{EXMS}.get_historic_trades', ght_mock) exchange = get_patched_exchange(mocker, default_conf) file1 = tmpdir1 / 'ETH_BTC-trades.json.gz' data_handler = get_datahandler(tmpdir1, data_format='jsongz') @@ -604,8 +597,7 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad file1.unlink() - mocker.patch('freqtrade.exchange.Exchange.get_historic_trades', - MagicMock(side_effect=ValueError)) + mocker.patch(f'{EXMS}.get_historic_trades', MagicMock(side_effect=ValueError)) assert not _download_trades_history(data_handler=data_handler, exchange=exchange, pair='ETH/BTC') @@ -615,8 +607,7 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad copyfile(testdatadir / file2.name, file2) ght_mock.reset_mock() - mocker.patch('freqtrade.exchange.Exchange.get_historic_trades', - ght_mock) + mocker.patch(f'{EXMS}.get_historic_trades', ght_mock) # Since before first start date since_time = int(trades_history[0][0] // 1000) - 500 timerange = TimeRange('date', None, since_time, 0) diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index e414d7624..be0346b78 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -14,7 +14,7 @@ from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.edge import Edge, PairInfo from freqtrade.enums import ExitType from freqtrade.exceptions import OperationalException -from tests.conftest import get_patched_freqtradebot, log_has +from tests.conftest import EXMS, get_patched_freqtradebot, log_has from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, _get_frame_time_from_offset) @@ -261,7 +261,7 @@ def mocked_load_data(datadir, pairs=[], timeframe='0m', def test_edge_process_downloaded_data(mocker, edge_conf): freqtrade = get_patched_freqtradebot(mocker, edge_conf) - mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001)) + mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.001)) mocker.patch('freqtrade.edge.edge_positioning.refresh_data', MagicMock()) mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data) edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) @@ -273,7 +273,7 @@ def test_edge_process_downloaded_data(mocker, edge_conf): def test_edge_process_no_data(mocker, edge_conf, caplog): freqtrade = get_patched_freqtradebot(mocker, edge_conf) - mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001)) + mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.001)) mocker.patch('freqtrade.edge.edge_positioning.refresh_data', MagicMock()) mocker.patch('freqtrade.edge.edge_positioning.load_data', MagicMock(return_value={})) edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) @@ -286,7 +286,7 @@ def test_edge_process_no_data(mocker, edge_conf, caplog): def test_edge_process_no_trades(mocker, edge_conf, caplog): freqtrade = get_patched_freqtradebot(mocker, edge_conf) - mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0.001) + mocker.patch(f'{EXMS}.get_fee', return_value=0.001) mocker.patch('freqtrade.edge.edge_positioning.refresh_data', ) mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data) # Return empty @@ -303,7 +303,7 @@ def test_edge_process_no_pairs(mocker, edge_conf, caplog): mocker.patch('freqtrade.freqtradebot.validate_config_consistency') freqtrade = get_patched_freqtradebot(mocker, edge_conf) - fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0.001) + fee_mock = mocker.patch(f'{EXMS}.get_fee', return_value=0.001) mocker.patch('freqtrade.edge.edge_positioning.refresh_data') mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data) # Return empty @@ -319,7 +319,7 @@ def test_edge_process_no_pairs(mocker, edge_conf, caplog): def test_edge_init_error(mocker, edge_conf,): edge_conf['stake_amount'] = 0.5 - mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001)) + mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.001)) with pytest.raises(OperationalException, match='Edge works only with unlimited stake amount'): get_patched_freqtradebot(mocker, edge_conf) diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index 79d3c0836..616910682 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -7,7 +7,7 @@ import pytest from freqtrade.enums import CandleType, MarginMode, TradingMode from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException -from tests.conftest import get_mock_coro, get_patched_exchange, log_has_re +from tests.conftest import EXMS, get_mock_coro, get_patched_exchange, log_has_re from tests.exchange.test_exchange import ccxt_exceptionhandlers @@ -34,8 +34,8 @@ def test_create_stoploss_order_binance(default_conf, mocker, limitratio, expecte default_conf['dry_run'] = False default_conf['margin_mode'] = MarginMode.ISOLATED default_conf['trading_mode'] = trademode - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') @@ -113,8 +113,8 @@ def test_create_stoploss_order_dry_run_binance(default_conf, mocker): api_mock = MagicMock() order_type = 'stop_loss_limit' default_conf['dry_run'] = True - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') @@ -600,7 +600,7 @@ def test_get_maintenance_ratio_and_amt_binance( mm_ratio, amt, ): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange._leverage_tiers = leverage_tiers (result_ratio, result_amt) = exchange.get_maintenance_ratio_and_amt(pair, nominal_value) diff --git a/tests/exchange/test_bitpanda.py b/tests/exchange/test_bitpanda.py index 4bd168e7e..de44be986 100644 --- a/tests/exchange/test_bitpanda.py +++ b/tests/exchange/test_bitpanda.py @@ -1,7 +1,7 @@ from datetime import datetime from unittest.mock import MagicMock -from tests.conftest import get_patched_exchange +from tests.conftest import EXMS, get_patched_exchange def test_get_trades_for_order(default_conf, mocker): @@ -9,7 +9,7 @@ def test_get_trades_for_order(default_conf, mocker): order_id = 'ABCD-ABCD' since = datetime(2018, 5, 5, 0, 0, 0) default_conf["dry_run"] = False - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) api_mock = MagicMock() api_mock.fetch_my_trades = MagicMock(return_value=[{'id': 'TTR67E-3PFBD-76IISV', diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index bbeb56c6a..f06a53308 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -17,7 +17,7 @@ from freqtrade.enums import CandleType from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date from freqtrade.exchange.exchange import Exchange, timeframe_to_msecs from freqtrade.resolvers.exchange_resolver import ExchangeResolver -from tests.conftest import get_default_conf_usdt +from tests.conftest import EXMS, get_default_conf_usdt EXCHANGE_FIXTURE_TYPE = Tuple[Exchange, str] @@ -322,13 +322,12 @@ def exchange_futures(request, exchange_conf, class_mocker): class_mocker.patch( 'freqtrade.exchange.binance.Binance.fill_leverage_tiers') - class_mocker.patch('freqtrade.exchange.exchange.Exchange.fetch_trading_fees') + class_mocker.patch(f'{EXMS}.fetch_trading_fees') class_mocker.patch('freqtrade.exchange.okx.Okx.additional_exchange_init') class_mocker.patch('freqtrade.exchange.binance.Binance.additional_exchange_init') class_mocker.patch('freqtrade.exchange.bybit.Bybit.additional_exchange_init') - class_mocker.patch('freqtrade.exchange.exchange.Exchange.load_cached_leverage_tiers', - return_value=None) - class_mocker.patch('freqtrade.exchange.exchange.Exchange.cache_leverage_tiers') + class_mocker.patch(f'{EXMS}.load_cached_leverage_tiers', return_value=None) + class_mocker.patch(f'{EXMS}.cache_leverage_tiers') exchange = ExchangeResolver.load_exchange( request.param, exchange_conf, validate=True, load_leverage_tiers=True) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index bdffa0d29..c9d1b6cab 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -22,8 +22,8 @@ from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_CO calculate_backoff, remove_credentials) from freqtrade.exchange.exchange import amount_to_contract_precision from freqtrade.resolvers.exchange_resolver import ExchangeResolver -from tests.conftest import (generate_test_data_raw, get_mock_coro, get_patched_exchange, log_has, - log_has_re, num_log_has_re) +from tests.conftest import (EXMS, generate_test_data_raw, get_mock_coro, get_patched_exchange, + log_has, log_has_re, num_log_has_re) # Make sure to always keep one exchange here which is NOT subclassed!! @@ -150,9 +150,9 @@ def test_remove_credentials(default_conf, caplog) -> None: def test_init_ccxt_kwargs(default_conf, mocker, caplog): - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - aei_mock = mocker.patch('freqtrade.exchange.Exchange.additional_exchange_init') + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_stakecurrency') + aei_mock = mocker.patch(f'{EXMS}.additional_exchange_init') caplog.set_level(logging.INFO) conf = copy.deepcopy(default_conf) @@ -218,12 +218,12 @@ def test_init_exception(default_conf, mocker): def test_exchange_resolver(default_conf, mocker, caplog): - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=MagicMock())) - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=MagicMock())) + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_pairs') + 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) assert isinstance(exchange, Exchange) @@ -362,9 +362,8 @@ def test_price_to_precision(price, precision_mode, precision, expected): def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precision, expected): markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': precision}}}) exchange = get_patched_exchange(mocker, default_conf, id="binance") - mocker.patch('freqtrade.exchange.Exchange.markets', markets) - mocker.patch('freqtrade.exchange.Exchange.precisionMode', - PropertyMock(return_value=precision_mode)) + mocker.patch(f'{EXMS}.markets', markets) + mocker.patch(f'{EXMS}.precisionMode', PropertyMock(return_value=precision_mode)) pair = 'ETH/BTC' assert pytest.approx(exchange.price_get_one_pip(pair, price)) == expected @@ -376,10 +375,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} # no pair found - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) with pytest.raises(ValueError, match=r'.*get market information.*'): exchange.get_min_pair_stake_amount('BNB/BTC', 1, stoploss) @@ -388,10 +384,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: 'cost': {'min': None, 'max': None}, 'amount': {'min': None, 'max': None}, } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) assert result is None result = exchange.get_max_pair_stake_amount('ETH/BTC', 1) @@ -402,10 +395,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: 'cost': {'min': 2, 'max': 10000}, 'amount': {'min': None, 'max': None}, } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) # min result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) expected_result = 2 * (1 + 0.05) / (1 - abs(stoploss)) @@ -422,10 +412,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: 'cost': {'min': None, 'max': None}, 'amount': {'min': 2, 'max': 10000}, } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) expected_result = 2 * 2 * (1 + 0.05) / (1 - abs(stoploss)) assert pytest.approx(result) == expected_result @@ -441,10 +428,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: 'cost': {'min': 2, 'max': None}, 'amount': {'min': 2, 'max': None}, } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) expected_result = max(2, 2 * 2) * (1 + 0.05) / (1 - abs(stoploss)) assert pytest.approx(result) == expected_result @@ -457,10 +441,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: 'cost': {'min': 8, 'max': 10000}, 'amount': {'min': 2, 'max': 500}, } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) expected_result = max(8, 2 * 2) * (1 + 0.05) / (1 - abs(stoploss)) assert pytest.approx(result) == expected_result @@ -496,10 +477,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: default_conf['trading_mode'] = 'futures' default_conf['margin_mode'] = 'isolated' exchange = get_patched_exchange(mocker, default_conf, id="binance") - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) # Contract size 0.01 result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1) @@ -509,10 +487,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: assert result == 10 markets["ETH/BTC"]["contractSize"] = '10' - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) # With Leverage, Contract size 10 result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) assert pytest.approx(result) == (expected_result / 12) * 10.0 @@ -531,10 +506,7 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: 'cost': {'min': 0.0001, 'max': 4000}, 'amount': {'min': 0.001, 'max': 10000}, } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss) expected_result = max(0.0001, 0.001 * 0.020405) * (1 + 0.05) / (1 - abs(stoploss)) assert round(result, 8) == round(expected_result, 8) @@ -592,12 +564,12 @@ def test_set_sandbox_exception(default_conf, mocker): def test__load_async_markets(default_conf, mocker, caplog): - mocker.patch('freqtrade.exchange.Exchange._init_ccxt') - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt') + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_markets') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') exchange = Exchange(default_conf) exchange._api_async.load_markets = get_mock_coro(None) exchange._load_async_markets() @@ -614,19 +586,19 @@ def test__load_markets(default_conf, mocker, caplog): caplog.set_level(logging.INFO) api_mock = MagicMock() api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError("SomeError")) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') Exchange(default_conf) assert log_has('Unable to initialize markets.', caplog) expected_return = {'ETH/BTC': 'available'} api_mock = MagicMock() api_mock.load_markets = MagicMock(return_value=expected_return) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) default_conf['exchange']['pair_whitelist'] = ['ETH/BTC'] ex = Exchange(default_conf) @@ -684,11 +656,11 @@ def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog): 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, 'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'}, }) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_pricing') Exchange(default_conf) @@ -699,17 +671,17 @@ def test_validate_stakecurrency_error(default_conf, mocker, caplog): 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, 'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'}, }) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') with pytest.raises(OperationalException, match=r'XRP is not available as stake on .*' 'Available currencies are: BTC, ETH, USDT'): Exchange(default_conf) type(api_mock).load_markets = MagicMock(side_effect=ccxt.NetworkError('No connection.')) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) with pytest.raises(OperationalException, match=r'Could not load markets, therefore cannot start\. Please.*'): @@ -757,11 +729,11 @@ def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs d id_mock = PropertyMock(return_value='test_exchange') type(api_mock).id = id_mock - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') Exchange(default_conf) @@ -770,10 +742,10 @@ def test_validate_pairs_not_available(default_conf, mocker): type(api_mock).markets = PropertyMock(return_value={ 'XRP/BTC': {'inactive': True, 'base': 'XRP', 'quote': 'BTC'} }) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}._load_async_markets') with pytest.raises(OperationalException, match=r'not available'): Exchange(default_conf) @@ -782,19 +754,19 @@ def test_validate_pairs_not_available(default_conf, mocker): def test_validate_pairs_exception(default_conf, mocker, caplog): caplog.set_level(logging.INFO) api_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance')) + mocker.patch(f'{EXMS}.name', PropertyMock(return_value='Binance')) type(api_mock).markets = PropertyMock(return_value={}) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') + mocker.patch(f'{EXMS}._init_ccxt', api_mock) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f'{EXMS}._load_async_markets') with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available on Binance'): Exchange(default_conf) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value={})) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) Exchange(default_conf) assert log_has('Unable to validate pairs (assuming they are correct).', caplog) @@ -806,11 +778,11 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog): 'XRP/BTC': {'quote': 'BTC', 'info': {'prohibitedIn': ['US']}}, 'NEO/BTC': {'quote': 'BTC', 'info': 'TestString'}, # info can also be a string ... }) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f'{EXMS}.validate_stakecurrency') Exchange(default_conf) assert log_has("Pair XRP/BTC is restricted for some users on this exchange." @@ -825,11 +797,11 @@ def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog): 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, 'HELLO-WORLD': {'quote': 'BTC'}, }) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') Exchange(default_conf) @@ -842,11 +814,11 @@ def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, ca 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, 'HELLO-WORLD': {'quote': 'BTC'}, }) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') Exchange(default_conf) assert type(api_mock).load_markets.call_count == 1 @@ -860,10 +832,10 @@ def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog): 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, 'HELLO-WORLD': {'quote': 'USDT'}, }) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_stakecurrency') with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"): Exchange(default_conf) @@ -883,11 +855,11 @@ def test_validate_timeframes(default_conf, mocker, timeframe): '1h': '1h'}) type(api_mock).timeframes = timeframes - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') Exchange(default_conf) @@ -903,9 +875,9 @@ def test_validate_timeframes_failed(default_conf, mocker): '1h': '1h'}) type(api_mock).timeframes = timeframes - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_pairs', MagicMock()) with pytest.raises(OperationalException, match=r"Invalid timeframe '3m'. This exchange supports.*"): Exchange(default_conf) @@ -925,10 +897,10 @@ def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker): # delete timeframes so magicmock does not autocreate it del api_mock.timeframes - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_stakecurrency') with pytest.raises(OperationalException, match=r'The ccxt library does not provide the list of timeframes ' r'for the exchange .* and this exchange ' @@ -945,11 +917,11 @@ def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker): # delete timeframes so magicmock does not autocreate it del api_mock.timeframes - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={'timeframes': None})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pairs', MagicMock()) + mocker.patch(f'{EXMS}.validate_stakecurrency') with pytest.raises(OperationalException, match=r'The ccxt library does not provide the list of timeframes ' r'for the exchange .* and this exchange ' @@ -969,12 +941,12 @@ def test_validate_timeframes_not_in_config(default_conf, mocker): '1h': '1h'}) type(api_mock).timeframes = timeframes - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange.validate_required_startup_candles') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f'{EXMS}.validate_required_startup_candles') Exchange(default_conf) @@ -985,13 +957,13 @@ def test_validate_pricing(default_conf, mocker): 'fetchTicker': True, } type(api_mock).has = PropertyMock(return_value=has) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_margin_mode') - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.name', 'Binance') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_trading_mode_and_margin_mode') + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.name', 'Binance') ExchangeResolver.load_exchange('binance', default_conf) has.update({'fetchTicker': False}) with pytest.raises(OperationalException, match="Ticker pricing not available for .*"): @@ -1020,13 +992,13 @@ def test_validate_ordertypes(default_conf, mocker): api_mock = MagicMock() type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True}) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f'{EXMS}.name', 'Bittrex') default_conf['order_types'] = { 'entry': 'limit', @@ -1037,7 +1009,7 @@ def test_validate_ordertypes(default_conf, mocker): Exchange(default_conf) type(api_mock).has = PropertyMock(return_value={'createMarketOrder': False}) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) default_conf['order_types'] = { 'entry': 'limit', @@ -1080,12 +1052,12 @@ def test_validate_ordertypes_stop_advanced(default_conf, mocker, exchange_name, default_conf['trading_mode'] = TradingMode.FUTURES default_conf['margin_mode'] = MarginMode.ISOLATED type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True}) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') default_conf['order_types'] = { 'entry': 'limit', 'exit': 'limit', @@ -1103,12 +1075,12 @@ def test_validate_ordertypes_stop_advanced(default_conf, mocker, exchange_name, def test_validate_order_types_not_in_config(default_conf, mocker): api_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f'{EXMS}.validate_stakecurrency') conf = copy.deepcopy(default_conf) Exchange(conf) @@ -1116,14 +1088,14 @@ def test_validate_order_types_not_in_config(default_conf, mocker): def test_validate_required_startup_candles(default_conf, mocker, caplog): api_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance')) + mocker.patch(f'{EXMS}.name', PropertyMock(return_value='Binance')) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch(f'{EXMS}._init_ccxt', api_mock) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f'{EXMS}.validate_stakecurrency') default_conf['startup_candle_count'] = 20 ex = Exchange(default_conf) @@ -1220,11 +1192,10 @@ def test_create_dry_run_order_fees( fee, ): mocker.patch( - 'freqtrade.exchange.Exchange.get_fee', - side_effect=lambda symbol, taker_or_maker: 2.0 if taker_or_maker == 'taker' else 1.0 + f'{EXMS}.get_fee', + side_effect=lambda symbol, taker_or_maker: 2.0 if taker_or_maker == 'taker' else 1.0 ) - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', - return_value=price_side == 'other') + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=price_side == 'other') exchange = get_patched_exchange(mocker, default_conf) order = exchange.create_dry_run_order( @@ -1241,8 +1212,7 @@ def test_create_dry_run_order_fees( else: assert order['fee'] is None - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', - return_value=price_side != 'other') + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=price_side != 'other') order1 = exchange.fetch_dry_run_order(order['id']) assert order1['fee']['rate'] == fee @@ -1303,8 +1273,7 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill order_book_l2_usd.reset_mock() # Empty orderbook test - mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', - return_value={'asks': [], 'bids': []}) + mocker.patch(f'{EXMS}.fetch_l2_order_book', return_value={'asks': [], 'bids': []}) exchange._dry_run_open_orders[order['id']]['status'] = 'open' order_closed = exchange.fetch_dry_run_order(order['id']) @@ -1372,8 +1341,8 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, }) default_conf['dry_run'] = False default_conf['margin_mode'] = 'isolated' - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange._set_leverage = MagicMock() exchange.set_margin_mode = MagicMock() @@ -1452,8 +1421,8 @@ def test_buy_prod(default_conf, mocker, exchange_name): } }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", @@ -1536,8 +1505,8 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): } }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order_type = 'limit' @@ -1602,8 +1571,8 @@ def test_sell_prod(default_conf, mocker, exchange_name): }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, @@ -1675,8 +1644,8 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): }) api_mock.options = {} default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order_type = 'limit' @@ -1742,7 +1711,7 @@ def test_get_balances_prod(default_conf, mocker, exchange_name): @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_fetch_positions(default_conf, mocker, exchange_name): - mocker.patch('freqtrade.exchange.Exchange.validate_trading_mode_and_margin_mode') + mocker.patch(f'{EXMS}.validate_trading_mode_and_margin_mode') api_mock = MagicMock() api_mock.fetch_positions = MagicMock(return_value=[ {'symbol': 'ETH/USDT:USDT', 'leverage': 5}, @@ -1798,7 +1767,7 @@ def test_fetch_trading_fees(default_conf, mocker): default_conf['trading_mode'] = TradingMode.FUTURES default_conf['margin_mode'] = MarginMode.ISOLATED api_mock.fetch_trading_fees = MagicMock(return_value=tick) - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert '1INCH/USDT:USDT' in exchange._trading_fees @@ -1813,7 +1782,7 @@ def test_fetch_trading_fees(default_conf, mocker): api_mock.fetch_trading_fees = MagicMock(return_value={}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.fetch_trading_fees() - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) assert exchange.fetch_trading_fees() == {} @@ -1833,7 +1802,7 @@ def test_fetch_bids_asks(default_conf, mocker): } exchange_name = 'binance' api_mock.fetch_bids_asks = MagicMock(return_value=tick) - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # retrieve original ticker bidsasks = exchange.fetch_bids_asks() @@ -1866,7 +1835,7 @@ def test_fetch_bids_asks(default_conf, mocker): api_mock.fetch_bids_asks = MagicMock(return_value={}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.fetch_bids_asks() - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) assert exchange.fetch_bids_asks() == {} @@ -1885,7 +1854,7 @@ def test_get_tickers(default_conf, mocker, exchange_name): 'last': 41, } } - mocker.patch('freqtrade.exchange.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) api_mock.fetch_tickers = MagicMock(return_value=tick) api_mock.fetch_bids_asks = MagicMock(return_value={}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) @@ -1928,7 +1897,7 @@ def test_get_tickers(default_conf, mocker, exchange_name): api_mock.fetch_bids_asks.reset_mock() default_conf['trading_mode'] = TradingMode.FUTURES default_conf['margin_mode'] = MarginMode.ISOLATED - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_tickers() @@ -1937,7 +1906,7 @@ def test_get_tickers(default_conf, mocker, exchange_name): api_mock.fetch_tickers.reset_mock() api_mock.fetch_bids_asks.reset_mock() - mocker.patch('freqtrade.exchange.exchange.Exchange.exchange_has', return_value=False) + mocker.patch(f'{EXMS}.exchange_has', return_value=False) assert exchange.get_tickers() == {} @@ -2191,7 +2160,7 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach time_machine.move_to(start + timedelta(hours=99, minutes=30)) exchange = get_patched_exchange(mocker, default_conf) - mocker.patch("freqtrade.exchange.Exchange.ohlcv_candle_limit", return_value=100) + mocker.patch(f"{EXMS}.ohlcv_candle_limit", return_value=100) assert exchange._startup_candle_count == 0 exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) @@ -2241,7 +2210,7 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach # New candle on exchange - return 100 candles - but skip one candle so we actually get 2 candles # in one go new_startdate = (start + timedelta(hours=2)).strftime('%Y-%m-%d %H:%M') - # mocker.patch("freqtrade.exchange.Exchange.ohlcv_candle_limit", return_value=100) + # mocker.patch(f"{EXMS}.ohlcv_candle_limit", return_value=100) ohlcv = generate_test_data_raw('1h', 100, new_startdate) exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) res = exchange.refresh_latest_ohlcv(pairs) @@ -2342,7 +2311,7 @@ async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog): "symbol=ETH-BTC&type=5min&startAt=1640268735&endAt=1640418735" "429 Too Many Requests" '{"code":"429000","msg":"Too Many Requests"}')) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kucoin") - mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='KuCoin')) + mocker.patch(f'{EXMS}.name', PropertyMock(return_value='KuCoin')) msg = "Kucoin 429 error, avoid triggering DDosProtection backoff delay" assert not num_log_has_re(msg, caplog) @@ -2500,8 +2469,7 @@ def test_get_entry_rate(mocker, default_conf, caplog, side, ask, bid, default_conf['entry_pricing']['price_last_balance'] = last_ab default_conf['entry_pricing']['price_side'] = side exchange = get_patched_exchange(mocker, default_conf) - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', - return_value={'ask': ask, 'last': last, 'bid': bid}) + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': ask, 'last': last, 'bid': bid}) assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=True) == expected assert not log_has("Using cached entry rate for ETH/BTC.", caplog) @@ -2522,8 +2490,7 @@ def test_get_exit_rate(default_conf, mocker, caplog, side, bid, ask, default_conf['exit_pricing']['price_side'] = side if last_ab is not None: default_conf['exit_pricing']['price_last_balance'] = last_ab - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', - return_value={'ask': ask, 'bid': bid, 'last': last}) + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': ask, 'bid': bid, 'last': last}) pair = "ETH/BTC" # Test regular mode @@ -2556,8 +2523,7 @@ def test_get_ticker_rate_error(mocker, entry, default_conf, caplog, side, is_sho default_conf['exit_pricing']['price_side'] = side default_conf['exit_pricing']['price_last_balance'] = last_ab exchange = get_patched_exchange(mocker, default_conf) - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', - return_value={'ask': ask, 'last': last, 'bid': bid}) + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': ask, 'last': last, 'bid': bid}) with pytest.raises(PricingError): exchange.get_rate('ETH/BTC', refresh=True, side=entry, is_short=is_short) @@ -2581,7 +2547,7 @@ def test_get_exit_rate_orderbook( default_conf['exit_pricing']['use_order_book'] = True default_conf['exit_pricing']['order_book_top'] = 1 pair = "ETH/BTC" - mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2) + mocker.patch(f'{EXMS}.fetch_l2_order_book', order_book_l2) exchange = get_patched_exchange(mocker, default_conf) rate = exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) assert not log_has("Using cached exit rate for ETH/BTC.", caplog) @@ -2599,8 +2565,7 @@ def test_get_exit_rate_orderbook_exception(default_conf, mocker, caplog): default_conf['exit_pricing']['order_book_top'] = 1 pair = "ETH/BTC" # Test What happens if the exchange returns an empty orderbook. - mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', - return_value={'bids': [[]], 'asks': [[]]}) + mocker.patch(f'{EXMS}.fetch_l2_order_book', return_value={'bids': [[]], 'asks': [[]]}) exchange = get_patched_exchange(mocker, default_conf) with pytest.raises(PricingError): exchange.get_rate(pair, refresh=True, side="exit", is_short=False) @@ -2614,8 +2579,7 @@ def test_get_exit_rate_exception(default_conf, mocker, is_short): # Ticker on one side can be empty in certain circumstances. default_conf['exit_pricing']['price_side'] = 'ask' pair = "ETH/BTC" - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', - return_value={'ask': None, 'bid': 0.12, 'last': None}) + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': None, 'bid': 0.12, 'last': None}) exchange = get_patched_exchange(mocker, default_conf) with pytest.raises(PricingError, match=r"Exit-Rate for ETH/BTC was empty."): exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) @@ -2623,8 +2587,7 @@ def test_get_exit_rate_exception(default_conf, mocker, is_short): exchange._config['exit_pricing']['price_side'] = 'bid' assert exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) == 0.12 # Reverse sides - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', - return_value={'ask': 0.13, 'bid': None, 'last': None}) + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': 0.13, 'bid': None, 'last': None}) with pytest.raises(PricingError, match=r"Exit-Rate for ETH/BTC was empty."): exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) @@ -2990,7 +2953,7 @@ async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog, @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_historic_trades(default_conf, mocker, caplog, exchange_name, trades_history): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) pair = 'ETH/BTC' @@ -3012,7 +2975,7 @@ def test_get_historic_trades(default_conf, mocker, caplog, exchange_name, trades @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange_name, trades_history): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=False) + mocker.patch(f'{EXMS}.exchange_has', return_value=False) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) pair = 'ETH/BTC' @@ -3028,7 +2991,7 @@ def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange def test_cancel_order_dry_run(default_conf, mocker, exchange_name): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True) + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True) assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {} assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {} @@ -3156,24 +3119,24 @@ def test_cancel_stoploss_order(default_conf, mocker, exchange_name): @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name): default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', return_value={'for': 123}) + mocker.patch(f'{EXMS}.fetch_stoploss_order', return_value={'for': 123}) mocker.patch('freqtrade.exchange.Gate.fetch_stoploss_order', return_value={'for': 123}) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) res = {'fee': {}, 'status': 'canceled', 'amount': 1234} - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', return_value=res) + mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value=res) mocker.patch('freqtrade.exchange.Gate.cancel_stoploss_order', return_value=res) co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co == res - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', return_value='canceled') + mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value='canceled') mocker.patch('freqtrade.exchange.Gate.cancel_stoploss_order', return_value='canceled') # Fall back to fetch_stoploss_order co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co == {'for': 123} exc = InvalidOrderException("") - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', side_effect=exc) + mocker.patch(f'{EXMS}.fetch_stoploss_order', side_effect=exc) mocker.patch('freqtrade.exchange.Gate.fetch_stoploss_order', side_effect=exc) co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co['amount'] == 555 @@ -3181,7 +3144,7 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name): with pytest.raises(InvalidOrderException): exc = InvalidOrderException("Did not find order") - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', side_effect=exc) + mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=exc) mocker.patch('freqtrade.exchange.Gate.cancel_stoploss_order', side_effect=exc) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123) @@ -3315,7 +3278,7 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name, trading_mode, default_conf["dry_run"] = False default_conf["trading_mode"] = trading_mode default_conf["margin_mode"] = 'isolated' - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) api_mock = MagicMock() api_mock.fetch_my_trades = MagicMock(return_value=[{'id': 'TTR67E-3PFBD-76IISV', @@ -3358,7 +3321,7 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name, trading_mode, 'get_trades_for_order', 'fetch_my_trades', order_id=order_id, pair='ETH/USDT:USDT', since=since) - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=False)) assert exchange.get_trades_for_order(order_id, 'ETH/USDT:USDT', since) == [] @@ -3550,7 +3513,7 @@ def test_get_markets(default_conf, mocker, markets_static, def test_get_markets_error(default_conf, mocker): ex = get_patched_exchange(mocker, default_conf) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=None)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=None)) with pytest.raises(OperationalException, match="Markets were not loaded."): ex.get_markets('LTC', 'USDT', True, False) @@ -3695,7 +3658,7 @@ def test_market_is_tradable( quote, spot, margin, futures, trademode, add_dict, exchange, expected_result ) -> None: default_conf['trading_mode'] = trademode - mocker.patch('freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_margin_mode') + mocker.patch(f'{EXMS}.validate_trading_mode_and_margin_mode') ex = get_patched_exchange(mocker, default_conf, id=exchange) market = { 'symbol': market_symbol, @@ -3740,7 +3703,7 @@ def test_order_has_fee(order, expected) -> None: (0.34, 'USDT', 0.01)), ]) def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None: - mocker.patch('freqtrade.exchange.Exchange.calculate_fee_rate', MagicMock(return_value=0.01)) + mocker.patch(f'{EXMS}.calculate_fee_rate', MagicMock(return_value=0.01)) ex = get_patched_exchange(mocker, default_conf) assert ex.extract_cost_curr_rate(order['fee'], order['symbol'], cost=20, amount=1) == expected @@ -3785,7 +3748,7 @@ def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None: 'fee': {'currency': None, 'cost': 0.005}}, None, None), ]) def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_rate) -> None: - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081}) + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'last': 0.081}) if unknown_fee_rate: default_conf['exchange']['unknown_fee_rate'] = unknown_fee_rate @@ -3857,7 +3820,7 @@ def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name): ]) type(api_mock).has = PropertyMock(return_value={'fetchFundingHistory': True}) - # mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', lambda pair, since: y) + # mocker.patch(f'{EXMS}.get_funding_fees', lambda pair, since: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) date_time = datetime.strptime("2021-09-01T00:00:01.000Z", '%Y-%m-%dT%H:%M:%S.%fZ') unix_time = int(date_time.timestamp()) @@ -4257,8 +4220,7 @@ def test__fetch_and_calculate_funding_fees( type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True}) ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) - mocker.patch('freqtrade.exchange.Exchange.timeframes', PropertyMock( - return_value=['1h', '4h', '8h'])) + mocker.patch(f'{EXMS}.timeframes', PropertyMock(return_value=['1h', '4h', '8h'])) funding_fees = ex._fetch_and_calculate_funding_fees( pair='ADA/USDT', amount=amount, is_short=True, open_date=d1, close_date=d2) assert pytest.approx(funding_fees) == expected_fees @@ -4268,7 +4230,7 @@ def test__fetch_and_calculate_funding_fees( assert pytest.approx(funding_fees) == -expected_fees # Return empty "refresh_latest" - mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", return_value={}) + mocker.patch(f"{EXMS}.refresh_latest_ohlcv", return_value={}) ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) with pytest.raises(ExchangeError, match="Could not find funding rates."): ex._fetch_and_calculate_funding_fees( @@ -4294,7 +4256,7 @@ def test__fetch_and_calculate_funding_fees_datetime_called( return_value=funding_rate_history_octohourly) type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True}) - mocker.patch('freqtrade.exchange.Exchange.timeframes', PropertyMock(return_value=['4h', '8h'])) + mocker.patch(f'{EXMS}.timeframes', PropertyMock(return_value=['4h', '8h'])) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) d1 = datetime.strptime("2021-09-01 00:00:00 +0000", '%Y-%m-%d %H:%M:%S %z') @@ -4317,7 +4279,7 @@ def test__get_contract_size(mocker, default_conf, pair, expected_size, trading_m default_conf['trading_mode'] = trading_mode default_conf['margin_mode'] = 'isolated' exchange = get_patched_exchange(mocker, default_conf, api_mock) - mocker.patch('freqtrade.exchange.Exchange.markets', { + mocker.patch(f'{EXMS}.markets', { 'LTC/USD': { 'symbol': 'LTC/USD', 'contractSize': None, @@ -4353,7 +4315,7 @@ def test__order_contracts_to_amount( api_mock = MagicMock() default_conf['trading_mode'] = trading_mode default_conf['margin_mode'] = 'isolated' - mocker.patch('freqtrade.exchange.Exchange.markets', markets) + mocker.patch(f'{EXMS}.markets', markets) exchange = get_patched_exchange(mocker, default_conf, api_mock) orders = [ @@ -4475,7 +4437,7 @@ def test__trades_contracts_to_amount( api_mock = MagicMock() default_conf['trading_mode'] = trading_mode default_conf['margin_mode'] = 'isolated' - mocker.patch('freqtrade.exchange.Exchange.markets', markets) + mocker.patch(f'{EXMS}.markets', markets) exchange = get_patched_exchange(mocker, default_conf, api_mock) trades = [ @@ -4511,7 +4473,7 @@ def test__amount_to_contracts( default_conf['trading_mode'] = 'spot' default_conf['margin_mode'] = 'isolated' exchange = get_patched_exchange(mocker, default_conf, api_mock) - mocker.patch('freqtrade.exchange.Exchange.markets', { + mocker.patch(f'{EXMS}.markets', { 'LTC/USD': { 'symbol': 'LTC/USD', 'contractSize': None, @@ -4769,7 +4731,7 @@ def test_get_max_pair_stake_amount( }, } - mocker.patch('freqtrade.exchange.Exchange.markets', markets) + 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 assert exchange.get_max_pair_stake_amount('LTC/USDT:USDT', 2.0) == float('inf') @@ -4779,7 +4741,7 @@ def test_get_max_pair_stake_amount( default_conf['trading_mode'] = 'spot' exchange = get_patched_exchange(mocker, default_conf, api_mock) - mocker.patch('freqtrade.exchange.Exchange.markets', markets) + mocker.patch(f'{EXMS}.markets', markets) assert exchange.get_max_pair_stake_amount('BTC/USDT', 2.0) == 20000 assert exchange.get_max_pair_stake_amount('ADA/USDT', 2.0) == 500 @@ -4790,7 +4752,7 @@ def test_load_leverage_tiers(mocker, default_conf, leverage_tiers, exchange_name api_mock.fetch_leverage_tiers = MagicMock() type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True}) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_margin_mode') + mocker.patch(f'{EXMS}.validate_trading_mode_and_margin_mode') api_mock.fetch_leverage_tiers = MagicMock(return_value={ 'ADA/USDT:USDT': [ @@ -4943,7 +4905,7 @@ def test_get_maintenance_ratio_and_amt_exceptions(mocker, default_conf, leverage api_mock = MagicMock() default_conf['trading_mode'] = 'futures' default_conf['margin_mode'] = 'isolated' - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange._leverage_tiers = leverage_tiers @@ -4980,7 +4942,7 @@ def test_get_maintenance_ratio_and_amt( api_mock = MagicMock() default_conf['trading_mode'] = 'futures' default_conf['margin_mode'] = 'isolated' - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange._leverage_tiers = leverage_tiers exchange.get_maintenance_ratio_and_amt(pair, value) == (mmr, maintAmt) @@ -5019,7 +4981,7 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers): @pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'gate', 'okx', 'bybit']) def test__get_params(mocker, default_conf, exchange_name): api_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange._params = {'test': True} @@ -5324,8 +5286,8 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun 'symbol': 'ETH/BTC', }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange.get_contract_size = MagicMock(return_value=contract_size) diff --git a/tests/exchange/test_gate.py b/tests/exchange/test_gate.py index f777dd7d0..db7591a40 100644 --- a/tests/exchange/test_gate.py +++ b/tests/exchange/test_gate.py @@ -7,18 +7,18 @@ from freqtrade.enums import MarginMode, TradingMode from freqtrade.exceptions import OperationalException from freqtrade.exchange import Gate from freqtrade.resolvers.exchange_resolver import ExchangeResolver -from tests.conftest import get_patched_exchange +from tests.conftest import EXMS, get_patched_exchange def test_validate_order_types_gate(default_conf, mocker): default_conf['exchange']['name'] = 'gate' - mocker.patch('freqtrade.exchange.Exchange._init_ccxt') - mocker.patch('freqtrade.exchange.Exchange._load_markets', return_value={}) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange.name', 'Gate') + mocker.patch(f'{EXMS}._init_ccxt') + mocker.patch(f'{EXMS}._load_markets', return_value={}) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f'{EXMS}.name', 'Gate') exch = ExchangeResolver.load_exchange('gate', default_conf, True) assert isinstance(exch, Gate) @@ -105,7 +105,7 @@ def test_stoploss_adjust_gate(mocker, default_conf, sl1, sl2, sl3, side): ('maker', 0.0, 0.0), ]) def test_fetch_my_trades_gate(mocker, default_conf, takerormaker, rate, cost): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) tick = {'ETH/USDT:USDT': { 'info': {'user_id': '', 'taker_fee': '0.0018', diff --git a/tests/exchange/test_huobi.py b/tests/exchange/test_huobi.py index e5fa986c3..5e4fd7316 100644 --- a/tests/exchange/test_huobi.py +++ b/tests/exchange/test_huobi.py @@ -5,7 +5,7 @@ import ccxt import pytest from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException -from tests.conftest import get_patched_exchange +from tests.conftest import EXMS, get_patched_exchange from tests.exchange.test_exchange import ccxt_exceptionhandlers @@ -26,8 +26,8 @@ def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected, } }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') @@ -79,8 +79,8 @@ def test_create_stoploss_order_dry_run_huobi(default_conf, mocker): api_mock = MagicMock() order_type = 'stop-limit' default_conf['dry_run'] = True - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 3a183de93..40a5a5b38 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -5,7 +5,7 @@ import ccxt import pytest from freqtrade.exceptions import DependencyException, InvalidOrderException -from tests.conftest import get_patched_exchange +from tests.conftest import EXMS, get_patched_exchange from tests.exchange.test_exchange import ccxt_exceptionhandlers @@ -28,8 +28,8 @@ def test_buy_kraken_trading_agreement(default_conf, mocker): }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") order = exchange.create_order( @@ -68,8 +68,8 @@ def test_sell_kraken_trading_agreement(default_conf, mocker): }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, @@ -191,8 +191,8 @@ def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adj }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') @@ -262,8 +262,8 @@ def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adj def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side): api_mock = MagicMock() default_conf['dry_run'] = True - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') diff --git a/tests/exchange/test_kucoin.py b/tests/exchange/test_kucoin.py index 0a57d728c..d2cb23091 100644 --- a/tests/exchange/test_kucoin.py +++ b/tests/exchange/test_kucoin.py @@ -5,7 +5,7 @@ import ccxt import pytest from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException -from tests.conftest import get_patched_exchange +from tests.conftest import EXMS, get_patched_exchange from tests.exchange.test_exchange import ccxt_exceptionhandlers @@ -26,8 +26,8 @@ def test_create_stoploss_order_kucoin(default_conf, mocker, limitratio, expected } }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') if order_type == 'limit': @@ -87,8 +87,8 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker): api_mock = MagicMock() order_type = 'market' default_conf['dry_run'] = True - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index 5565bbed2..cdfc943af 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -13,7 +13,7 @@ from freqtrade.freqai.utils import download_all_data_for_training, get_required_ from freqtrade.optimize.backtesting import Backtesting from freqtrade.persistence import Trade from freqtrade.plugins.pairlistmanager import PairListManager -from tests.conftest import create_mock_trades, get_patched_exchange, log_has_re +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 @@ -520,7 +520,7 @@ def test_get_state_info(mocker, freqai_conf, dp_exists, caplog, tickers): strategy = get_patched_freqai_strategy(mocker, freqai_conf) exchange = get_patched_exchange(mocker, freqai_conf) ticker_mock = MagicMock(return_value=tickers()['ETH/BTC']) - mocker.patch("freqtrade.exchange.Exchange.fetch_ticker", ticker_mock) + mocker.patch(f"{EXMS}.fetch_ticker", ticker_mock) strategy.dp = DataProvider(freqai_conf, exchange) if not dp_exists: diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 4e78fc139..90efc40b1 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -8,7 +8,7 @@ from freqtrade.data.history import get_timerange from freqtrade.enums import ExitType from freqtrade.optimize.backtesting import Backtesting from freqtrade.persistence.trade_model import LocalTrade -from tests.conftest import patch_exchange +from tests.conftest import EXMS, patch_exchange from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, _get_frame_time_from_offset, tests_timeframe) @@ -921,9 +921,9 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer) default_conf["use_exit_signal"] = data.use_exit_signal default_conf["max_open_trades"] = 10 - mocker.patch("freqtrade.exchange.Exchange.get_fee", return_value=0.0) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_fee", return_value=0.0) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch("freqtrade.exchange.Binance.get_max_leverage", return_value=100) patch_exchange(mocker) frame = _build_backtest_dataframe(data.data) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index e407b4173..8dee45b6d 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -26,8 +26,8 @@ from freqtrade.optimize.backtest_caching import get_strategy_run_id from freqtrade.optimize.backtesting import Backtesting from freqtrade.persistence import LocalTrade, Trade from freqtrade.resolvers import StrategyResolver -from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re, patch_exchange, - patched_configuration_load_config_file) +from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_args, log_has, log_has_re, + patch_exchange, patched_configuration_load_config_file) ORDER_TYPES = [ @@ -245,7 +245,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog) def test_start(mocker, fee, default_conf, caplog) -> None: start_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) patch_exchange(mocker) mocker.patch('freqtrade.optimize.backtesting.Backtesting.start', start_mock) patched_configuration_load_config_file(mocker, default_conf) @@ -269,7 +269,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: """ default_conf["order_types"] = order_types patch_exchange(mocker) - get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) + get_fee = mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.5)) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) assert backtesting.config == default_conf @@ -290,7 +290,7 @@ def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY, 'HyperoptableStrategy'] - mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) + mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.5)) with pytest.raises(OperationalException, match=r"Timeframe needs to be set in either configuration"): Backtesting(default_conf) @@ -300,7 +300,7 @@ def test_data_with_fee(default_conf, mocker) -> None: patch_exchange(mocker) default_conf['fee'] = 0.1234 - fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) + fee_mock = mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.5)) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) assert backtesting.fee == 0.1234 @@ -404,7 +404,7 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) -> def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> None: - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) mocker.patch('freqtrade.data.history.history_utils.load_pair_history', MagicMock(return_value=pd.DataFrame())) mocker.patch('freqtrade.data.history.get_timerange', get_timerange) @@ -436,9 +436,9 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, tickers) -> None: - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.get_tickers', tickers) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) mocker.patch('freqtrade.data.history.get_timerange', get_timerange) patch_exchange(mocker) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest') @@ -474,9 +474,9 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti def test_backtest__enter_trade(default_conf, fee, mocker) -> None: default_conf['use_exit_signal'] = False - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=0.00001) + mocker.patch(f'{EXMS}.get_max_pair_stake_amount', return_value=float('inf')) patch_exchange(mocker) default_conf['stake_amount'] = 'unlimited' default_conf['max_open_trades'] = 2 @@ -525,7 +525,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: assert trade.stake_amount == 495 assert trade.is_short is True - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=300.0) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=300.0) trade = backtesting._enter_trade(pair, row=row, direction='long') assert trade assert trade.stake_amount == 300.0 @@ -533,10 +533,10 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None: default_conf_usdt['use_exit_signal'] = False - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch("freqtrade.exchange.Exchange.get_max_leverage", return_value=100) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_max_leverage", return_value=100) mocker.patch("freqtrade.optimize.backtesting.price_to_precision", lambda p, *args: p) patch_exchange(mocker) default_conf_usdt['stake_amount'] = 300 @@ -564,7 +564,7 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None: ] backtesting.strategy.leverage = MagicMock(return_value=5.0) - mocker.patch("freqtrade.exchange.Exchange.get_maintenance_ratio_and_amt", + mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.01, 0.01)) # leverage = 5 @@ -601,7 +601,7 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None: assert pytest.approx(trade.liquidation_price) == 0.11787191 # Stake-amount too high! - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=600.0) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=600.0) trade = backtesting._enter_trade(pair, row=row, direction='long') assert trade is None @@ -616,9 +616,9 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None: def test_backtest__check_trade_exit(default_conf, fee, mocker) -> None: default_conf['use_exit_signal'] = False - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) default_conf['timeframe_detail'] = '1m' default_conf['max_open_trades'] = 2 @@ -681,9 +681,9 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: default_conf['use_exit_signal'] = False default_conf['max_open_trades'] = 10 - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -766,9 +766,9 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: @pytest.mark.parametrize('use_detail', [True, False]) def test_backtest_one_detail(default_conf_usdt, fee, mocker, testdatadir, use_detail) -> None: default_conf_usdt['use_exit_signal'] = False - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) if use_detail: default_conf_usdt['timeframe_detail'] = '1m' patch_exchange(mocker) @@ -854,12 +854,12 @@ def test_backtest_one_detail_futures( default_conf_usdt['margin_mode'] = 'isolated' default_conf_usdt['candle_type_def'] = CandleType.FUTURES - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['XRP/USDT:USDT'])) - mocker.patch("freqtrade.exchange.Exchange.get_maintenance_ratio_and_amt", + mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.01, 0.01)) default_conf_usdt['timeframe'] = '1h' if use_detail: @@ -945,12 +945,12 @@ def test_backtest_one_detail_futures_funding_fees( default_conf_usdt['minimal_roi'] = {'0': 1} default_conf_usdt['dry_run_wallet'] = 100000 - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['XRP/USDT:USDT'])) - mocker.patch("freqtrade.exchange.Exchange.get_maintenance_ratio_and_amt", + mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.01, 0.01)) default_conf_usdt['timeframe'] = '1h' if use_detail: @@ -1010,9 +1010,9 @@ def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir) default_conf['startup_candle_count'] = 0 # Cancel unfilled order after 4 minutes on 5m timeframe. default_conf["unfilledtimeout"] = {"entry": 4} - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) default_conf['max_open_trades'] = 1 backtesting = Backtesting(default_conf) @@ -1035,9 +1035,9 @@ def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir) def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None: default_conf['use_exit_signal'] = False default_conf['max_open_trades'] = 1 - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -1061,9 +1061,9 @@ def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> N default_conf['use_exit_signal'] = False default_conf['max_open_trades'] = 10 - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -1105,9 +1105,9 @@ def test_processed(default_conf, mocker, testdatadir) -> None: def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadir) -> None: default_conf['use_exit_signal'] = False default_conf['max_open_trades'] = 10 - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=100000) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=100000) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -1155,9 +1155,9 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad default_conf['enable_protections'] = True default_conf['timeframe'] = '1m' default_conf['max_open_trades'] = 1 - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) tests = [ ['sine', 9], ['raise', 10], @@ -1203,9 +1203,9 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir, default_conf['protections'] = protections default_conf['enable_protections'] = True - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) # While entry-signals are unrealistic, running backtesting # over and over again should not cause different results @@ -1262,9 +1262,9 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir): def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) default_conf['max_open_trades'] = 10 backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC', datadir=testdatadir) @@ -1310,9 +1310,9 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) dataframe['exit_short'] = 0 return dataframe - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) patch_exchange(mocker) pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC'] diff --git a/tests/optimize/test_backtesting_adjust_position.py b/tests/optimize/test_backtesting_adjust_position.py index 23b5eb93b..9fc726bd1 100644 --- a/tests/optimize/test_backtesting_adjust_position.py +++ b/tests/optimize/test_backtesting_adjust_position.py @@ -12,17 +12,17 @@ from freqtrade.data import history from freqtrade.data.history import get_timerange from freqtrade.enums import ExitType from freqtrade.optimize.backtesting import Backtesting -from tests.conftest import patch_exchange +from tests.conftest import EXMS, patch_exchange def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> None: default_conf['use_exit_signal'] = False default_conf['max_open_trades'] = 10 - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) mocker.patch('freqtrade.optimize.backtesting.amount_to_contract_precision', lambda x, *args, **kwargs: round(x, 8)) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) default_conf.update({ "stake_amount": 100.0, @@ -99,10 +99,10 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> ]) def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, leverage) -> None: default_conf['use_exit_signal'] = False - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=10) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch("freqtrade.exchange.Exchange.get_max_leverage", return_value=10) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=10) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_max_leverage", return_value=10) patch_exchange(mocker) default_conf.update({ diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index 8241a5362..64172bf1c 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -6,7 +6,7 @@ from unittest.mock import MagicMock from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge from freqtrade.enums import RunMode from freqtrade.optimize.edge_cli import EdgeCli -from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, patch_exchange, +from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_args, log_has, patch_exchange, patched_configuration_load_config_file) @@ -71,7 +71,7 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N def test_start(mocker, fee, edge_conf, caplog) -> None: start_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) patch_exchange(mocker) mocker.patch('freqtrade.optimize.edge_cli.EdgeCli.start', start_mock) patched_configuration_load_config_file(mocker, edge_conf) @@ -101,7 +101,7 @@ def test_edge_init_fee(mocker, edge_conf) -> None: patch_exchange(mocker) edge_conf['fee'] = 0.1234 edge_conf['stake_amount'] = 20 - fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) + fee_mock = mocker.patch(f'{EXMS}.get_fee', return_value=0.5) edge_cli = EdgeCli(edge_conf) assert edge_cli.edge.fee == 0.1234 assert fee_mock.call_count == 0 diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 36ceaeab2..998798580 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -20,7 +20,7 @@ from freqtrade.optimize.hyperopt_tools import HyperoptTools from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.optimize.space import SKDecimal from freqtrade.strategy import IntParameter -from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, get_markets, log_has, log_has_re, +from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_args, get_markets, log_has, log_has_re, patch_exchange, patched_configuration_load_config_file) @@ -859,7 +859,7 @@ def test_simplified_interface_failed(mocker, hyperopt_conf, space) -> None: def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None: patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) (Path(tmpdir) / 'hyperopt_results').mkdir(parents=True) # No hyperopt needed hyperopt_conf.update({ @@ -897,10 +897,10 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None: def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmpdir, fee) -> None: - mocker.patch('freqtrade.exchange.Exchange.validate_config', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch('freqtrade.exchange.Exchange._load_markets') - mocker.patch('freqtrade.exchange.Exchange.markets', + mocker.patch(f'{EXMS}.validate_config', MagicMock()) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f'{EXMS}._load_markets') + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=get_markets())) (Path(tmpdir) / 'hyperopt_results').mkdir(parents=True) # No hyperopt needed @@ -938,7 +938,7 @@ def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmpdir, def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmpdir, fee) -> None: patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) (Path(tmpdir) / 'hyperopt_results').mkdir(parents=True) hyperopt_conf.update({ @@ -996,7 +996,7 @@ def test_stake_amount_unlimited_max_open_trades(mocker, hyperopt_conf, tmpdir, f # This test is to ensure that unlimited max_open_trades are ignored for the backtesting # if we have an unlimited stake amount patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) (Path(tmpdir) / 'hyperopt_results').mkdir(parents=True) hyperopt_conf.update({ 'strategy': 'HyperoptableStrategy', @@ -1024,7 +1024,7 @@ def test_max_open_trades_dump(mocker, hyperopt_conf, tmpdir, fee, capsys) -> Non # This test is to ensure that after hyperopting, max_open_trades is never # saved as inf in the output json params patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) (Path(tmpdir) / 'hyperopt_results').mkdir(parents=True) hyperopt_conf.update({ 'strategy': 'HyperoptableStrategy', @@ -1070,7 +1070,7 @@ def test_max_open_trades_consistency(mocker, hyperopt_conf, tmpdir, fee) -> None # This test is to ensure that max_open_trades is the same across all functions needing it # after it has been changed from the hyperopt patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0) + mocker.patch(f'{EXMS}.get_fee', return_value=0) (Path(tmpdir) / 'hyperopt_results').mkdir(parents=True) hyperopt_conf.update({ diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 739c3a7ac..2a7d4ccec 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -18,8 +18,8 @@ from freqtrade.persistence import Trade from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist, expand_pairlist from freqtrade.plugins.pairlistmanager import PairListManager from freqtrade.resolvers import PairListResolver -from tests.conftest import (create_mock_trades_usdt, get_patched_exchange, get_patched_freqtradebot, - log_has, log_has_re, num_log_has) +from tests.conftest import (EXMS, create_mock_trades_usdt, get_patched_exchange, + get_patched_freqtradebot, log_has, log_has_re, num_log_has) # Exclude RemotePairList from tests. @@ -139,7 +139,7 @@ def test_log_cached(mocker, static_pl_conf, markets, tickers): def test_load_pairlist_noexist(mocker, markets, default_conf): freqtrade = get_patched_freqtradebot(mocker, default_conf) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) plm = PairListManager(freqtrade.exchange, default_conf, MagicMock()) with pytest.raises(OperationalException, match=r"Impossible to load Pairlist 'NonexistingPairList'. " @@ -150,7 +150,7 @@ def test_load_pairlist_noexist(mocker, markets, default_conf): def test_load_pairlist_verify_multi(mocker, markets_static, default_conf): freqtrade = get_patched_freqtradebot(mocker, default_conf) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets_static)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets_static)) plm = PairListManager(freqtrade.exchange, default_conf, MagicMock()) # Call different versions one after the other, should always consider what was passed in # and have no side-effects (therefore the same check multiple times) @@ -166,7 +166,7 @@ def test_refresh_market_pair_not_in_whitelist(mocker, markets, static_pl_conf): freqtrade = get_patched_freqtradebot(mocker, static_pl_conf) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) freqtrade.pairlists.refresh_pairlist() # List ordered by BaseVolume whitelist = ['ETH/BTC', 'TKN/BTC'] @@ -324,7 +324,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): exchange_has=MagicMock(return_value=True), ) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets_empty)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets_empty)) # argument: use the whitelist dynamically by exchange-volume whitelist = [] @@ -523,7 +523,7 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t ('HOT/BTC', '1d', CandleType.SPOT): ohlcv_history_high_vola, } - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) mocker.patch.multiple('freqtrade.exchange.Exchange', @@ -649,7 +649,7 @@ def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, ('HOT/BTC', '1d', CandleType.SPOT): ohlcv_history_high_volume, } - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) if volumefilter_result == 'default_refresh_too_short': with pytest.raises(OperationalException, @@ -702,7 +702,7 @@ def test_PrecisionFilter_error(mocker, whitelist_conf) -> None: whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PrecisionFilter"}] del whitelist_conf['stoploss'] - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) with pytest.raises(OperationalException, match=r"PrecisionFilter can only work with stoploss defined\..*"): @@ -713,7 +713,7 @@ def test_PerformanceFilter_error(mocker, whitelist_conf, caplog) -> None: whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PerformanceFilter"}] if hasattr(Trade, 'query'): del Trade.query - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) exchange = get_patched_exchange(mocker, whitelist_conf) pm = PairListManager(exchange, whitelist_conf, MagicMock()) pm.refresh_pairlist() @@ -755,7 +755,7 @@ def test_PerformanceFilter_lookback(mocker, default_conf_usdt, fee, caplog) -> N {"method": "StaticPairList"}, {"method": "PerformanceFilter", "minutes": 60, "min_profit": 0.01} ] - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) exchange = get_patched_exchange(mocker, default_conf_usdt) pm = PairListManager(exchange, default_conf_usdt) pm.refresh_pairlist() @@ -781,7 +781,7 @@ def test_PerformanceFilter_keep_mid_order(mocker, default_conf_usdt, fee, caplog {"method": "StaticPairList", "allow_inactive": True}, {"method": "PerformanceFilter", "minutes": 60, } ] - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf_usdt) pm = PairListManager(exchange, default_conf_usdt) pm.refresh_pairlist() @@ -881,7 +881,7 @@ def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist def test__whitelist_for_active_markets_empty(mocker, whitelist_conf, pairlist, tickers): whitelist_conf['pairlists'][0]['method'] = pairlist - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) mocker.patch.multiple('freqtrade.exchange.Exchange', @@ -897,7 +897,7 @@ def test__whitelist_for_active_markets_empty(mocker, whitelist_conf, pairlist, t def test_volumepairlist_invalid_sortvalue(mocker, whitelist_conf): whitelist_conf['pairlists'][0].update({"sort_key": "asdf"}) - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) with pytest.raises(OperationalException, match=r"key asdf not in .*"): get_patched_freqtradebot(mocker, whitelist_conf) @@ -1000,14 +1000,14 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o ('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history, ('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history.iloc[[0]], } - mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data) + mocker.patch(f'{EXMS}.refresh_latest_ohlcv', return_value=ohlcv_data) freqtrade.pairlists.refresh_pairlist() assert len(freqtrade.pairlists.whitelist) == 3 assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 1 # Move to next day t.move_to("2021-09-02 01:00:00 +00:00") - mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data) + mocker.patch(f'{EXMS}.refresh_latest_ohlcv', return_value=ohlcv_data) freqtrade.pairlists.refresh_pairlist() assert len(freqtrade.pairlists.whitelist) == 3 assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 1 @@ -1021,7 +1021,7 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o ('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history, ('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history, } - mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data) + mocker.patch(f'{EXMS}.refresh_latest_ohlcv', return_value=ohlcv_data) freqtrade.pairlists.refresh_pairlist() assert len(freqtrade.pairlists.whitelist) == 4 # Called once (only for XRP/BTC) @@ -1033,7 +1033,7 @@ def test_OffsetFilter_error(mocker, whitelist_conf) -> None: [{"method": "StaticPairList"}, {"method": "OffsetFilter", "offset": -1}] ) - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) with pytest.raises(OperationalException, match=r'OffsetFilter requires offset to be >= 0'): @@ -1214,7 +1214,7 @@ def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig, def test_pairlistmanager_no_pairlist(mocker, whitelist_conf): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) whitelist_conf['pairlists'] = [] @@ -1266,7 +1266,7 @@ def test_performance_filter(mocker, whitelist_conf, pairlists, pair_allowlist, o allowlist_conf['pairlists'] = pairlists allowlist_conf['exchange']['pair_whitelist'] = pair_allowlist - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, allowlist_conf) mocker.patch.multiple('freqtrade.exchange.Exchange', @@ -1371,7 +1371,7 @@ def test_expand_pairlist_keep_invalid(wildcardlist, pairs, expected): def test_ProducerPairlist_no_emc(mocker, whitelist_conf): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) whitelist_conf['pairlists'] = [ { @@ -1388,7 +1388,7 @@ def test_ProducerPairlist_no_emc(mocker, whitelist_conf): def test_ProducerPairlist(mocker, whitelist_conf, markets): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) mocker.patch.multiple('freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 3eb391edd..40d14212f 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -12,8 +12,8 @@ from freqtrade.persistence import Trade from freqtrade.persistence.pairlock_middleware import PairLocks from freqtrade.rpc import RPC, RPCException from freqtrade.rpc.fiat_convert import CryptoToFiatConverter -from tests.conftest import (create_mock_trades, create_mock_trades_usdt, get_patched_freqtradebot, - patch_get_signal) +from tests.conftest import (EXMS, create_mock_trades, create_mock_trades_usdt, + get_patched_freqtradebot, patch_get_signal) def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: @@ -169,7 +169,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: response = deepcopy(gen_response) assert results[0] == response - mocker.patch('freqtrade.exchange.Exchange.get_rate', + mocker.patch(f'{EXMS}.get_rate', MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))) results = rpc._rpc_trade_status() assert isnan(results[0]['profit_ratio']) @@ -209,7 +209,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: freqtradebot.state = State.RUNNING with pytest.raises(RPCException, match=r'.*no active trade*'): rpc._rpc_status_table(default_conf['stake_currency'], 'USD') - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=False) + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False) freqtradebot.enter_positions() result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') @@ -220,7 +220,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: assert '0.00' == result[0][3] assert isnan(fiat_profit_sum) - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True) + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True) freqtradebot.process() result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') @@ -231,7 +231,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: assert '-0.41%' == result[0][3] assert isnan(fiat_profit_sum) - # Test with fiatconvert + # Test with fiat convert rpc._fiat_converter = CryptoToFiatConverter() result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') assert "Since" in headers @@ -251,7 +251,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: # 3 on top of the initial one. assert result[0][4] == '1/4' - mocker.patch('freqtrade.exchange.Exchange.get_rate', + mocker.patch(f'{EXMS}.get_rate', MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))) result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') assert 'instantly' == result[0][2] @@ -367,15 +367,13 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short): assert stoploss_mock.call_count == 1 assert res['cancel_order_count'] == 2 - stoploss_mock = mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', - side_effect=InvalidOrderException) + stoploss_mock = mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=InvalidOrderException) res = rpc._rpc_delete('3') assert stoploss_mock.call_count == 1 stoploss_mock.reset_mock() - cancel_mock = mocker.patch('freqtrade.exchange.Exchange.cancel_order', - side_effect=InvalidOrderException) + cancel_mock = mocker.patch(f'{EXMS}.cancel_order', side_effect=InvalidOrderException) res = rpc._rpc_delete('4') assert cancel_mock.call_count == 1 @@ -423,7 +421,7 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None: assert stats['best_rate'] == 10.0 # Test non-available pair - mocker.patch('freqtrade.exchange.Exchange.get_rate', + mocker.patch(f'{EXMS}.get_rate', MagicMock(side_effect=ExchangeError("Pair 'XRP/USDT' not available"))) stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) assert stats['trade_count'] == 7 @@ -708,15 +706,14 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: freqtradebot.state = State.RUNNING assert cancel_order_mock.call_count == 0 - mocker.patch( - 'freqtrade.exchange.Exchange._dry_is_price_crossed', MagicMock(return_value=False)) + mocker.patch(f'{EXMS}._dry_is_price_crossed', MagicMock(return_value=False)) freqtradebot.enter_positions() # make an limit-buy open trade trade = Trade.query.filter(Trade.id == '3').first() filled_amount = trade.amount / 2 # Fetch order - it's open first, and closed after cancel_order is called. mocker.patch( - 'freqtrade.exchange.Exchange.fetch_order', + f'{EXMS}.fetch_order', side_effect=[{ 'id': trade.orders[0].order_id, 'status': 'open', @@ -738,7 +735,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: assert pytest.approx(trade.amount) == filled_amount mocker.patch( - 'freqtrade.exchange.Exchange.fetch_order', + f'{EXMS}.fetch_order', return_value={ 'status': 'open', 'type': 'limit', @@ -752,7 +749,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: amount = trade.amount # make an limit-buy open trade, if there is no 'filled', don't sell it mocker.patch( - 'freqtrade.exchange.Exchange.fetch_order', + f'{EXMS}.fetch_order', return_value={ 'status': 'open', 'type': 'limit', @@ -770,7 +767,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: # make an limit-sell open trade mocker.patch( - 'freqtrade.exchange.Exchange.fetch_order', + f'{EXMS}.fetch_order', return_value={ 'status': 'open', 'type': 'limit', @@ -1138,7 +1135,7 @@ def test_rpc_whitelist_dynamic(mocker, default_conf) -> None: default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 4, }] - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 67156da45..46cfd07d7 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -24,7 +24,7 @@ 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 tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_mock_coro, +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) @@ -473,9 +473,9 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers): ftbot, client = botclient ftbot.config['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) - mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) - mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', + mocker.patch(f'{EXMS}.get_balances', return_value=rpc_balance) + mocker.patch(f'{EXMS}.get_tickers', tickers) + mocker.patch(f'{EXMS}.get_valid_pair_combination', side_effect=lambda a, b: f"{a}/{b}") ftbot.wallets.update() @@ -731,15 +731,13 @@ def test_api_delete_open_order(botclient, mocker, fee, markets, ticker, is_short assert_response(rc, 502) assert 'No open order for trade_id' in rc.json()['error'] trade = Trade.get_trades([Trade.id == 6]).first() - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - side_effect=ExchangeError) + mocker.patch(f'{EXMS}.fetch_order', side_effect=ExchangeError) rc = client_delete(client, f"{BASE_URI}/trades/6/open-order") assert_response(rc, 502) assert 'Order not found.' in rc.json()['error'] trade = Trade.get_trades([Trade.id == 6]).first() - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - return_value=trade.orders[-1].to_ccxt_object()) + mocker.patch(f'{EXMS}.fetch_order', return_value=trade.orders[-1].to_ccxt_object()) rc = client_delete(client, f"{BASE_URI}/trades/6/open-order") assert_response(rc) @@ -1068,7 +1066,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, 'orders': [ANY], } - mocker.patch('freqtrade.exchange.Exchange.get_rate', + mocker.patch(f'{EXMS}.get_rate', MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))) rc = client_get(client, f"{BASE_URI}/status") @@ -1631,7 +1629,7 @@ def test_sysinfo(botclient): def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): ftbot, client = botclient - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) rc = client_get(client, f"{BASE_URI}/backtest") # Backtest prevented in default mode diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 65e676860..84ca1914e 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -30,9 +30,9 @@ from freqtrade.persistence.models import Order from freqtrade.rpc import RPC from freqtrade.rpc.rpc import RPCException from freqtrade.rpc.telegram import Telegram, authorized_only -from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, create_mock_trades_usdt, - get_patched_freqtradebot, log_has, log_has_re, patch_exchange, - patch_get_signal, patch_whitelist) +from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades, + create_mock_trades_usdt, get_patched_freqtradebot, log_has, log_has_re, + patch_exchange, patch_get_signal, patch_whitelist) class DummyCls(Telegram): @@ -706,7 +706,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f msg_mock.reset_mock() # Update the ticker with a market going up - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', ticker_sell_up) + mocker.patch(f'{EXMS}.fetch_ticker', ticker_sell_up) # Simulate fulfilled LIMIT_SELL order for trade oobj = Order.parse_from_ccxt_object( limit_sell_order_usdt, limit_sell_order_usdt['symbol'], 'sell') @@ -764,10 +764,9 @@ def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) -> def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tickers) -> None: default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) - mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) - mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', - side_effect=lambda a, b: f"{a}/{b}") + mocker.patch(f'{EXMS}.get_balances', return_value=rpc_balance) + mocker.patch(f'{EXMS}.get_tickers', tickers) + mocker.patch(f'{EXMS}.get_valid_pair_combination', side_effect=lambda a, b: f"{a}/{b}") telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -790,7 +789,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick def test_balance_handle_empty_response(default_conf, update, mocker) -> None: default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) + mocker.patch(f'{EXMS}.get_balances', return_value={}) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -803,7 +802,7 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None: def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None: - mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) + mocker.patch(f'{EXMS}.get_balances', return_value={}) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -949,7 +948,7 @@ def test_telegram_forceexit_handle(default_conf, update, ticker, fee, assert trade # Increase the price and sell it - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', ticker_sell_up) + mocker.patch(f'{EXMS}.fetch_ticker', ticker_sell_up) # /forceexit 1 context = MagicMock() @@ -1492,7 +1491,7 @@ def test_whitelist_static(default_conf, update, mocker) -> None: def test_whitelist_dynamic(default_conf, update, mocker) -> None: - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 4 }] @@ -1707,8 +1706,7 @@ def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short, msg_mock.reset_mock() trade = Trade.get_trades([Trade.id == 6]).first() - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - return_value=trade.orders[-1].to_ccxt_object()) + mocker.patch(f'{EXMS}.fetch_order', return_value=trade.orders[-1].to_ccxt_object()) context = MagicMock() context.args = [6] telegram._cancel_open_order(update=update, context=context) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index dc4539401..a80937e1a 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -22,9 +22,10 @@ from freqtrade.persistence import Order, PairLocks, Trade from freqtrade.persistence.models import PairLock from freqtrade.plugins.protections.iprotection import ProtectionReturn from freqtrade.worker import Worker -from tests.conftest import (create_mock_trades, create_mock_trades_usdt, get_patched_freqtradebot, - get_patched_worker, log_has, log_has_re, patch_edge, patch_exchange, - patch_get_signal, patch_wallet, patch_whitelist) +from tests.conftest import (EXMS, create_mock_trades, create_mock_trades_usdt, + get_patched_freqtradebot, get_patched_worker, log_has, log_has_re, + patch_edge, patch_exchange, patch_get_signal, patch_wallet, + patch_whitelist) from tests.conftest_trades import (MOCK_TRADE_COUNT, entry_side, exit_side, mock_order_1, mock_order_2, mock_order_2_sell, mock_order_3, mock_order_3_sell, mock_order_4, mock_order_5_stoploss, mock_order_6_sell) @@ -46,7 +47,7 @@ def patch_RPCManager(mocker) -> MagicMock: def test_freqtradebot_state(mocker, default_conf_usdt, markets) -> None: - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) assert freqtrade.state is State.RUNNING @@ -862,8 +863,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, order['cost'] = 300 order['id'] = '444' - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=order)) + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=order)) assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) trade = Trade.query.all()[2] trade.is_short = is_short @@ -881,8 +881,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, order['average'] = 0.5 order['cost'] = 10.0 order['id'] = '555' - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=order)) + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=order)) assert freqtrade.execute_entry(pair, stake_amount) trade = Trade.query.all()[3] trade.is_short = is_short @@ -919,19 +918,18 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, order['average'] = 0.5 order['cost'] = 0.0 order['id'] = '66' - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=order)) + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=order)) assert not freqtrade.execute_entry(pair, stake_amount) assert freqtrade.strategy.leverage.call_count == 0 if trading_mode == 'spot' else 2 # Fail to get price... - mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0)) + mocker.patch(f'{EXMS}.get_rate', MagicMock(return_value=0.0)) with pytest.raises(PricingError, match="Could not determine entry price."): freqtrade.execute_entry(pair, stake_amount, is_short=is_short) # In case of custom entry price - mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50) + mocker.patch(f'{EXMS}.get_rate', return_value=0.50) order['status'] = 'open' order['id'] = '5566' freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508 @@ -1066,11 +1064,11 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho patch_exchange(mocker) order = limit_order[entry_side(is_short)] mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order) - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) + mocker.patch(f'{EXMS}.fetch_order', return_value=order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) stoploss = MagicMock(return_value={'id': 13434334}) - mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss) + mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', stoploss) freqtrade = FreqtradeBot(default_conf_usdt) freqtrade.strategy.order_types['stoploss_on_exchange'] = True @@ -1135,7 +1133,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ trade.stoploss_order_id = "100" hanging_stoploss_order = MagicMock(return_value={'status': 'open'}) - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', hanging_stoploss_order) + mocker.patch(f'{EXMS}.fetch_stoploss_order', hanging_stoploss_order) assert freqtrade.handle_stoploss_on_exchange(trade) is False assert trade.stoploss_order_id == "100" @@ -1148,7 +1146,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ trade.stoploss_order_id = "100" canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'}) - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', canceled_stoploss_order) + mocker.patch(f'{EXMS}.fetch_stoploss_order', canceled_stoploss_order) stoploss.reset_mock() assert freqtrade.handle_stoploss_on_exchange(trade) is False @@ -1183,17 +1181,14 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ 'average': 2, 'amount': enter_order['amount'], }) - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hit) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit) assert freqtrade.handle_stoploss_on_exchange(trade) is True assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog) assert trade.stoploss_order_id is None assert trade.is_open is False caplog.clear() - mocker.patch( - 'freqtrade.exchange.Exchange.create_stoploss', - side_effect=ExchangeError() - ) + mocker.patch(f'{EXMS}.create_stoploss', side_effect=ExchangeError()) trade.is_open = True freqtrade.handle_stoploss_on_exchange(trade) assert log_has('Unable to place a stoploss order on exchange.', caplog) @@ -1203,9 +1198,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ # It should try to add stoploss order trade.stoploss_order_id = 100 stoploss.reset_mock() - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', - side_effect=InvalidOrderException()) - mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss) + mocker.patch(f'{EXMS}.fetch_stoploss_order', side_effect=InvalidOrderException()) + mocker.patch(f'{EXMS}.create_stoploss', stoploss) freqtrade.handle_stoploss_on_exchange(trade) assert stoploss.call_count == 1 @@ -1214,8 +1208,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ trade.stoploss_order_id = None trade.is_open = False stoploss.reset_mock() - mocker.patch('freqtrade.exchange.Exchange.fetch_order') - mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss) + mocker.patch(f'{EXMS}.fetch_order') + mocker.patch(f'{EXMS}.create_stoploss', stoploss) assert freqtrade.handle_stoploss_on_exchange(trade) is False assert stoploss.call_count == 0 @@ -1237,10 +1231,10 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ freqtrade.config['trailing_stop'] = True stoploss = MagicMock(side_effect=InvalidOrderException()) - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order_with_result', + mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', side_effect=InvalidOrderException()) - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_cancelled) - mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_cancelled) + mocker.patch(f'{EXMS}.create_stoploss', stoploss) assert freqtrade.handle_stoploss_on_exchange(trade) is False assert trade.stoploss_order_id is None assert trade.is_open is False @@ -1269,7 +1263,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, get_fee=fee, ) mocker.patch.multiple( - 'freqtrade.exchange.Binance', + 'freqtrade.exchange.binance.Binance', fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': 100}), create_stoploss=MagicMock(side_effect=ExchangeError()), ) @@ -1313,7 +1307,7 @@ def test_create_stoploss_order_invalid_order( get_fee=fee, ) mocker.patch.multiple( - 'freqtrade.exchange.Binance', + 'freqtrade.exchange.binance.Binance', fetch_order=MagicMock(return_value={'status': 'canceled'}), create_stoploss=MagicMock(side_effect=InvalidOrderException()), ) @@ -1366,7 +1360,7 @@ def test_create_stoploss_order_insufficient_funds( fetch_order=MagicMock(return_value={'status': 'canceled'}), ) mocker.patch.multiple( - 'freqtrade.exchange.Binance', + 'freqtrade.exchange.binance.Binance', create_stoploss=MagicMock(side_effect=InsufficientFundsError()), ) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) @@ -1416,7 +1410,7 @@ def test_handle_stoploss_on_exchange_trailing( get_fee=fee, ) mocker.patch.multiple( - 'freqtrade.exchange.Binance', + 'freqtrade.exchange.binance.Binance', create_stoploss=stoploss, stoploss_adjust=MagicMock(return_value=True), ) @@ -1459,7 +1453,7 @@ def test_handle_stoploss_on_exchange_trailing( } }) - mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hanging) + mocker.patch('freqtrade.exchange.binance.Binance.fetch_stoploss_order', stoploss_order_hanging) # stoploss initially at 5% assert freqtrade.handle_trade(trade) is False @@ -1467,7 +1461,7 @@ def test_handle_stoploss_on_exchange_trailing( # price jumped 2x mocker.patch( - 'freqtrade.exchange.Exchange.fetch_ticker', + f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': bid[0], 'ask': ask[0], @@ -1477,8 +1471,8 @@ def test_handle_stoploss_on_exchange_trailing( cancel_order_mock = MagicMock() stoploss_order_mock = MagicMock(return_value={'id': 'so1'}) - mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock) - mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock) + mocker.patch('freqtrade.exchange.binance.Binance.cancel_stoploss_order', cancel_order_mock) + mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', stoploss_order_mock) # stoploss should not be updated as the interval is 60 seconds assert freqtrade.handle_trade(trade) is False @@ -1506,7 +1500,7 @@ def test_handle_stoploss_on_exchange_trailing( # price fell below stoploss, so dry-run sells trade. mocker.patch( - 'freqtrade.exchange.Exchange.fetch_ticker', + f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': bid[1], 'ask': ask[1], @@ -1541,7 +1535,7 @@ def test_handle_stoploss_on_exchange_trailing_error( get_fee=fee, ) mocker.patch.multiple( - 'freqtrade.exchange.Binance', + 'freqtrade.exchange.binance.Binance', create_stoploss=stoploss, stoploss_adjust=MagicMock(return_value=True), ) @@ -1579,9 +1573,9 @@ def test_handle_stoploss_on_exchange_trailing_error( 'stopPrice': '0.1' } } - mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', + mocker.patch('freqtrade.exchange.binance.Binance.cancel_stoploss_order', side_effect=InvalidOrderException()) - mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', + mocker.patch('freqtrade.exchange.binance.Binance.fetch_stoploss_order', return_value=stoploss_order_hanging) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/USDT.*", caplog) @@ -1610,7 +1604,7 @@ def test_stoploss_on_exchange_price_rounding( stoploss_mock = MagicMock(return_value={'id': '13434334'}) adjust_mock = MagicMock(return_value=False) mocker.patch.multiple( - 'freqtrade.exchange.Binance', + 'freqtrade.exchange.binance.Binance', create_stoploss=stoploss_mock, stoploss_adjust=adjust_mock, price_to_precision=price_mock, @@ -1649,7 +1643,7 @@ def test_handle_stoploss_on_exchange_custom_stop( get_fee=fee, ) mocker.patch.multiple( - 'freqtrade.exchange.Binance', + 'freqtrade.exchange.binance.Binance', create_stoploss=stoploss, stoploss_adjust=MagicMock(return_value=True), ) @@ -1692,14 +1686,14 @@ def test_handle_stoploss_on_exchange_custom_stop( } }) - mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hanging) + mocker.patch('freqtrade.exchange.binance.Binance.fetch_stoploss_order', stoploss_order_hanging) assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False # price jumped 2x mocker.patch( - 'freqtrade.exchange.Exchange.fetch_ticker', + f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': 4.38 if not is_short else 1.9 / 2, 'ask': 4.4 if not is_short else 2.2 / 2, @@ -1709,8 +1703,8 @@ def test_handle_stoploss_on_exchange_custom_stop( cancel_order_mock = MagicMock() stoploss_order_mock = MagicMock(return_value={'id': 'so1'}) - mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock) - mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock) + mocker.patch('freqtrade.exchange.binance.Binance.cancel_stoploss_order', cancel_order_mock) + mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', stoploss_order_mock) # stoploss should not be updated as the interval is 60 seconds assert freqtrade.handle_trade(trade) is False @@ -1740,7 +1734,7 @@ def test_handle_stoploss_on_exchange_custom_stop( # price fell below stoploss, so dry-run sells trade. mocker.patch( - 'freqtrade.exchange.Exchange.fetch_ticker', + f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': 4.17, 'ask': 4.19, @@ -1817,7 +1811,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde 'stopPrice': '2.178' }) - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hanging) # stoploss initially at 20% as edge dictated it. assert freqtrade.handle_trade(trade) is False @@ -1826,11 +1820,11 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde cancel_order_mock = MagicMock() stoploss_order_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock) - mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock) + mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock) + mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', stoploss_order_mock) # price goes down 5% - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={ + mocker.patch(f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': 2.19 * 0.95, 'ask': 2.2 * 0.95, 'last': 2.19 * 0.95 @@ -1845,7 +1839,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde cancel_order_mock.assert_not_called() # price jumped 2x - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={ + mocker.patch(f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': 4.38, 'ask': 4.4, 'last': 4.38 @@ -1895,9 +1889,8 @@ def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - return_value=limit_order[entry_side(is_short)]) - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) + mocker.patch(f'{EXMS}.fetch_order', return_value=limit_order[entry_side(is_short)]) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) # TODO: should not be magicmock trade = MagicMock() @@ -1921,7 +1914,7 @@ def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog def test_exit_positions_exception(mocker, default_conf_usdt, limit_order, caplog, is_short) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) order = limit_order[entry_side(is_short)] - mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order) + mocker.patch(f'{EXMS}.fetch_order', return_value=order) # TODO: should not be magicmock trade = MagicMock() @@ -1947,8 +1940,8 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, ca order = limit_order[entry_side(is_short)] mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order) - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) + mocker.patch(f'{EXMS}.fetch_order', return_value=order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=0.0) order_id = order['id'] @@ -1999,7 +1992,7 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, ca limit_buy_order_usdt_new['status'] = 'canceled' mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', side_effect=ValueError) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt_new) + mocker.patch(f'{EXMS}.fetch_order', return_value=limit_buy_order_usdt_new) res = freqtrade.update_trade_state(trade, order_id) # Cancelled empty assert res is True @@ -2018,9 +2011,9 @@ def test_update_trade_state_withorderdict( trades_for_order[0]['amount'] = initial_amount order_id = "oid_123456" order['id'] = order_id - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) # fetch_order should not be called!! - mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(side_effect=ValueError)) patch_exchange(mocker) amount = sum(x['amount'] for x in trades_for_order) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) @@ -2062,7 +2055,7 @@ def test_update_trade_state_exception(mocker, default_conf_usdt, is_short, limit caplog) -> None: order = limit_order[entry_side(is_short)] freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order) + mocker.patch(f'{EXMS}.fetch_order', return_value=order) # TODO: should not be magicmock trade = MagicMock() @@ -2080,8 +2073,7 @@ def test_update_trade_state_exception(mocker, default_conf_usdt, is_short, limit def test_update_trade_state_orderexception(mocker, default_conf_usdt, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - MagicMock(side_effect=InvalidOrderException)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(side_effect=InvalidOrderException)) # TODO: should not be magicmock trade = MagicMock() @@ -2101,9 +2093,9 @@ def test_update_trade_state_sell( buy_order = limit_order[entry_side(is_short)] open_order = limit_order_open[exit_side(is_short)] l_order = limit_order[exit_side(is_short)] - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) # fetch_order should not be called!! - mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(side_effect=ValueError)) wallet_mock = MagicMock() mocker.patch('freqtrade.wallets.Wallets.update', wallet_mock) @@ -2685,7 +2677,7 @@ def test_manage_open_orders_exit_usercustom( rpc_mock = patch_RPCManager(mocker) cancel_order_mock = MagicMock() patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_min_pair_stake_amount', return_value=0.0) + mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=0.0) et_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit') mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -3013,7 +3005,7 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_ del cancel_buy_order['filled'] cancel_order_mock = MagicMock(return_value=cancel_buy_order) - mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock) + mocker.patch(f'{EXMS}.cancel_order_with_result', cancel_order_mock) freqtrade = FreqtradeBot(default_conf_usdt) freqtrade._notify_enter_cancel = MagicMock() @@ -3044,11 +3036,11 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_ # Order remained open for some reason (cancel failed) cancel_buy_order['status'] = 'open' cancel_order_mock = MagicMock(return_value=cancel_buy_order) - mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock) + mocker.patch(f'{EXMS}.cancel_order_with_result', cancel_order_mock) assert not freqtrade.handle_cancel_enter(trade, l_order, reason) assert log_has_re(r"Order .* for .* not cancelled.", caplog) # min_pair_stake empty should not crash - mocker.patch('freqtrade.exchange.Exchange.get_min_pair_stake_amount', return_value=None) + mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=None) assert not freqtrade.handle_cancel_enter(trade, limit_order[entry_side(is_short)], reason) @@ -3060,9 +3052,9 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_sho patch_RPCManager(mocker) patch_exchange(mocker) cancel_order_mock = mocker.patch( - 'freqtrade.exchange.Exchange.cancel_order_with_result', + f'{EXMS}.cancel_order_with_result', return_value=limit_buy_order_canceled_empty) - nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter_cancel') + notify_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter_cancel') freqtrade = FreqtradeBot(default_conf_usdt) reason = CANCEL_REASON['TIMEOUT'] @@ -3077,7 +3069,7 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_sho r'Removing .* from database\.', caplog ) - assert nofiy_mock.call_count == 1 + assert notify_mock.call_count == 1 @pytest.mark.parametrize("is_short", [False, True]) @@ -3114,7 +3106,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order l_order['filled'] = 1.0 order = deepcopy(l_order) order['status'] = 'canceled' - mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order) + mocker.patch(f'{EXMS}.fetch_order', return_value=order) assert not freqtrade.handle_cancel_enter(trade, l_order, reason) assert cancel_order_mock.call_count == 1 @@ -3127,8 +3119,8 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None: 'freqtrade.exchange.Exchange', cancel_order=cancel_order_mock, ) - mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.245441) - mocker.patch('freqtrade.exchange.Exchange.get_min_pair_stake_amount', return_value=0.2) + mocker.patch(f'{EXMS}.get_rate', return_value=0.245441) + mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=0.2) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_order_fee') @@ -3230,9 +3222,8 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None: def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None: patch_RPCManager(mocker) patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_min_pair_stake_amount', return_value=0.0) - mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', - side_effect=InvalidOrderException()) + mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=0.0) + mocker.patch(f'{EXMS}.cancel_order_with_result', side_effect=InvalidOrderException()) freqtrade = FreqtradeBot(default_conf_usdt) @@ -3555,8 +3546,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( def test_execute_trade_exit_sloe_cancel_exception( mocker, default_conf_usdt, ticker_usdt, fee, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', - side_effect=InvalidOrderException()) + mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=InvalidOrderException()) mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=300)) create_order_mock = MagicMock(side_effect=[ {'id': '12345554'}, @@ -3668,7 +3658,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( } }) - mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss) + mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', stoploss) freqtrade = FreqtradeBot(default_conf_usdt) freqtrade.strategy.order_types['stoploss_on_exchange'] = True @@ -3707,7 +3697,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( "fee": None, "trades": None }) - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_executed) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_executed) freqtrade.exit_positions(trades) assert trade.stoploss_order_id is None @@ -4127,7 +4117,7 @@ def test_trailing_stop_loss(default_conf_usdt, limit_order_open, assert freqtrade.handle_trade(trade) is False # Raise praise into profits - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', + mocker.patch(f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': 2.0 * val1, 'ask': 2.0 * val1, @@ -4138,7 +4128,7 @@ def test_trailing_stop_loss(default_conf_usdt, limit_order_open, assert freqtrade.handle_trade(trade) is False caplog.clear() # Price fell - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', + mocker.patch(f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': 2.0 * val2, 'ask': 2.0 * val2, @@ -4208,7 +4198,7 @@ def test_trailing_stop_loss_positive( # Raise ticker_usdt above buy price mocker.patch( - 'freqtrade.exchange.Exchange.fetch_ticker', + f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': enter_price + (-0.06 if is_short else 0.06), 'ask': enter_price + (-0.06 if is_short else 0.06), @@ -4230,7 +4220,7 @@ def test_trailing_stop_loss_positive( caplog.clear() mocker.patch( - 'freqtrade.exchange.Exchange.fetch_ticker', + f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': enter_price + (-0.135 if is_short else 0.125), 'ask': enter_price + (-0.135 if is_short else 0.125), @@ -4246,7 +4236,7 @@ def test_trailing_stop_loss_positive( assert log_has("ETH/USDT - Adjusting stoploss...", caplog) mocker.patch( - 'freqtrade.exchange.Exchange.fetch_ticker', + f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': enter_price + (-0.02 if is_short else 0.02), 'ask': enter_price + (-0.02 if is_short else 0.02), @@ -4312,7 +4302,7 @@ def test_disable_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limi def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fee, fee, caplog, mocker): - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) amount = sum(x['amount'] for x in trades_for_order) trade = Trade( pair='LTC/ETH', @@ -4338,7 +4328,7 @@ def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fe def test_get_real_amount_quote_dust(default_conf_usdt, trades_for_order, buy_order_fee, fee, caplog, mocker): - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) walletmock = mocker.patch('freqtrade.wallets.Wallets.update') mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=8.1122) amount = sum(x['amount'] for x in trades_for_order) @@ -4363,7 +4353,7 @@ def test_get_real_amount_quote_dust(default_conf_usdt, trades_for_order, buy_ord def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mocker, fee): - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) amount = buy_order_fee['amount'] trade = Trade( @@ -4417,7 +4407,7 @@ def test_get_real_amount( buy_order['fee'] = fee_par trades_for_order[0]['fee'] = fee_par - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) amount = sum(x['amount'] for x in trades_for_order) trade = Trade( pair='LTC/ETH', @@ -4431,7 +4421,7 @@ def test_get_real_amount( freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) if not use_ticker_usdt_rate: - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', side_effect=ExchangeError) + mocker.patch(f'{EXMS}.fetch_ticker', side_effect=ExchangeError) caplog.clear() order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') @@ -4463,7 +4453,7 @@ def test_get_real_amount_multi( if fee_currency: trades_for_order[0]['fee']['currency'] = fee_currency - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) amount = float(sum(x['amount'] for x in trades_for_order)) default_conf_usdt['stake_currency'] = "ETH" @@ -4480,8 +4470,8 @@ def test_get_real_amount_multi( # Fake markets entry to enable fee parsing markets['BNB/ETH'] = markets['ETH/USDT'] freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': 0.19, 'last': 0.2}) # Amount is reduced by "fee" @@ -4510,7 +4500,7 @@ def test_get_real_amount_invalid_order(default_conf_usdt, trades_for_order, buy_ limit_buy_order_usdt = deepcopy(buy_order_fee) limit_buy_order_usdt['fee'] = {'cost': 0.004} - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) amount = float(sum(x['amount'] for x in trades_for_order)) trade = Trade( pair='LTC/ETH', @@ -4531,9 +4521,9 @@ def test_get_real_amount_invalid_order(default_conf_usdt, trades_for_order, buy_ def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_doublefee, fee, mocker): - tfo_mock = mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) - mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', return_value='BNB/USDT') - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 200}) + tfo_mock = mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) + mocker.patch(f'{EXMS}.get_valid_pair_combination', return_value='BNB/USDT') + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'last': 200}) trade = Trade( pair='LTC/USDT', amount=30.0, @@ -4559,7 +4549,7 @@ def test_get_real_amount_wrong_amount(default_conf_usdt, trades_for_order, buy_o limit_buy_order_usdt = deepcopy(buy_order_fee) limit_buy_order_usdt['amount'] = limit_buy_order_usdt['amount'] - 0.001 - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) amount = float(sum(x['amount'] for x in trades_for_order)) trade = Trade( pair='LTC/ETH', @@ -4584,7 +4574,7 @@ def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_ord limit_buy_order_usdt = deepcopy(buy_order_fee) trades_for_order[0]['amount'] = trades_for_order[0]['amount'] + 1e-15 - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) amount = float(sum(x['amount'] for x in trades_for_order)) trade = Trade( pair='LTC/ETH', @@ -4664,7 +4654,7 @@ def test_get_real_amount_in_point(default_conf_usdt, buy_order_fee, fee, mocker, ] }] - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades) amount = float(sum(x['amount'] for x in trades)) trade = Trade( pair='CEL/USDT', @@ -4746,7 +4736,7 @@ def test_order_book_depth_of_market( default_conf_usdt['entry_pricing']['check_depth_of_market']['bids_to_ask_delta'] = delta patch_RPCManager(mocker) patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2) + mocker.patch(f'{EXMS}.fetch_l2_order_book', order_book_l2) mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker_usdt, @@ -4843,7 +4833,7 @@ def test_order_book_exit_pricing( """ test order book ask strategy """ - mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2) + mocker.patch(f'{EXMS}.fetch_l2_order_book', order_book_l2) default_conf_usdt['exchange']['name'] = 'binance' default_conf_usdt['exit_pricing']['use_order_book'] = True default_conf_usdt['exit_pricing']['order_book_top'] = 1 @@ -4884,8 +4874,7 @@ def test_order_book_exit_pricing( assert freqtrade.handle_trade(trade) is True assert trade.close_rate_requested == order_book_l2.return_value['asks'][0][0] - mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', - return_value={'bids': [[]], 'asks': [[]]}) + mocker.patch(f'{EXMS}.fetch_l2_order_book', return_value={'bids': [[]], 'asks': [[]]}) with pytest.raises(PricingError): freqtrade.handle_trade(trade) assert log_has_re( @@ -4897,14 +4886,14 @@ def test_startup_state(default_conf_usdt, mocker): default_conf_usdt['pairlist'] = {'method': 'VolumePairList', 'config': {'number_assets': 20} } - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) worker = get_patched_worker(mocker, default_conf_usdt) assert worker.freqtrade.state is State.RUNNING def test_startup_trade_reinit(default_conf_usdt, edge_conf, mocker): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) reinit_mock = MagicMock() mocker.patch('freqtrade.persistence.Trade.stoploss_reinitialization', reinit_mock) @@ -4961,7 +4950,7 @@ def test_cancel_all_open_orders(mocker, default_conf_usdt, fee, limit_order, lim is_short, buy_calls, sell_calls): default_conf_usdt['cancel_open_orders_on_exit'] = True mocker.patch( - 'freqtrade.exchange.Exchange.fetch_order', + f'{EXMS}.fetch_order', side_effect=[ ExchangeError(), limit_order[exit_side(is_short)], @@ -5017,17 +5006,17 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_s matching_buy_order.update({ 'status': 'closed', }) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=matching_buy_order) + mocker.patch(f'{EXMS}.fetch_order', return_value=matching_buy_order) freqtrade.startup_update_open_orders() # Only stoploss and sell orders are kept open assert len(Order.get_open_orders()) == 2 caplog.clear() - mocker.patch('freqtrade.exchange.Exchange.fetch_order', side_effect=ExchangeError) + mocker.patch(f'{EXMS}.fetch_order', side_effect=ExchangeError) freqtrade.startup_update_open_orders() assert log_has_re(r"Error updating Order .*", caplog) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', side_effect=InvalidOrderException) + mocker.patch(f'{EXMS}.fetch_order', side_effect=InvalidOrderException) hto_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_order') # Orders which are no longer found after X days should be assumed as canceled. freqtrade.startup_update_open_orders() @@ -5072,7 +5061,7 @@ def test_update_trades_without_assigned_fees(mocker, default_conf_usdt, fee, is_ 'currency': order['symbol'].split('/')[0]}}) return order - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', side_effect=[ patch_with_fee(mock_order_2_sell(is_short=is_short)), patch_with_fee(mock_order_3_sell(is_short=is_short)), @@ -5132,8 +5121,7 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_sh freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state') - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', - return_value={'status': 'open'}) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', return_value={'status': 'open'}) create_mock_trades(fee, is_short) trades = Trade.get_trades().all() @@ -5173,7 +5161,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state') - mock_fo = mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mock_fo = mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', return_value={'status': 'open'}) def reset_open_orders(trade): @@ -5259,7 +5247,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap caplog.clear() # Test error case - mock_fo = mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mock_fo = mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', side_effect=ExchangeError()) order = mock_order_5_stoploss(is_short=is_short) @@ -5439,8 +5427,7 @@ def test_update_funding_fees( return ret - mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', - side_effect=refresh_latest_ohlcv_mock) + mocker.patch(f'{EXMS}.refresh_latest_ohlcv', side_effect=refresh_latest_ohlcv_mock) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -5467,7 +5454,7 @@ def test_update_funding_fees( assert len(trades) == 3 for trade in trades: assert pytest.approx(trade.funding_fees) == 0 - mocker.patch('freqtrade.exchange.Exchange.create_order', return_value=open_exit_order) + mocker.patch(f'{EXMS}.create_order', return_value=open_exit_order) time_machine.move_to("2021-09-01 08:00:00 +00:00") if schedule_off: for trade in trades: @@ -5497,7 +5484,7 @@ def test_update_funding_fees( def test_update_funding_fees_error(mocker, default_conf, caplog): - mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', side_effect=ExchangeError()) + mocker.patch(f'{EXMS}.get_funding_fees', side_effect=ExchangeError()) default_conf['trading_mode'] = 'futures' default_conf['margin_mode'] = 'isolated' freqtrade = get_patched_freqtradebot(mocker, default_conf) @@ -5551,9 +5538,8 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: 'id': '650', 'order_id': '650' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_successful_buy_order)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_successful_buy_order)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_successful_buy_order)) assert freqtrade.execute_entry(pair, stake_amount) # Should create an closed trade with an no open order id @@ -5603,10 +5589,8 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: 'id': '651', 'order_id': '651' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=open_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', - MagicMock(return_value=open_dca_order_1)) + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=open_dca_order_1)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=open_dca_order_1)) assert freqtrade.execute_entry(pair, stake_amount, trade=trade) orders = Order.query.all() @@ -5637,9 +5621,9 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: # Assume it does nothing since order is still open fetch_order_mm = MagicMock(side_effect=make_sure_its_651) - mocker.patch('freqtrade.exchange.Exchange.create_order', fetch_order_mm) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', fetch_order_mm) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', fetch_order_mm) + mocker.patch(f'{EXMS}.create_order', fetch_order_mm) + mocker.patch(f'{EXMS}.fetch_order', fetch_order_mm) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', fetch_order_mm) freqtrade.update_trades_without_assigned_fees() orders = Order.query.all() @@ -5679,11 +5663,9 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: 'datetime': arrow.utcnow().isoformat(), } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - MagicMock(return_value=closed_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_dca_order_1)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(return_value=closed_dca_order_1)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_dca_order_1)) freqtrade.manage_open_orders() @@ -5723,11 +5705,9 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: 'id': '652', 'order_id': '652' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_dca_order_2)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - MagicMock(return_value=closed_dca_order_2)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_dca_order_2)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(return_value=closed_dca_order_2)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_dca_order_2)) assert freqtrade.execute_entry(pair, stake_amount, trade=trade) @@ -5761,11 +5741,9 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: 'id': '653', 'order_id': '653' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_sell_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - MagicMock(return_value=closed_sell_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_sell_dca_order_1)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(return_value=closed_sell_dca_order_1)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_sell_dca_order_1)) assert freqtrade.execute_trade_exit(trade=trade, limit=8, exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT), @@ -5839,9 +5817,8 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: 'id': '600', 'order_id': '600' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_successful_buy_order)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_successful_buy_order)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_successful_buy_order)) assert freqtrade.execute_entry(pair, amount) # Should create an closed trade with an no open order id @@ -5894,11 +5871,9 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: 'id': '601', 'order_id': '601' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_sell_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - MagicMock(return_value=closed_sell_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_sell_dca_order_1)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(return_value=closed_sell_dca_order_1)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_sell_dca_order_1)) assert freqtrade.execute_trade_exit(trade=trade, limit=ask, exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT), @@ -5940,11 +5915,9 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: 'id': '602', 'order_id': '602' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_sell_dca_order_2)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - MagicMock(return_value=closed_sell_dca_order_2)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_sell_dca_order_2)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(return_value=closed_sell_dca_order_2)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_sell_dca_order_2)) assert freqtrade.execute_trade_exit(trade=trade, limit=ask, exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT), @@ -6034,9 +6007,8 @@ def test_position_adjust3(mocker, default_conf_usdt, fee, data) -> None: 'id': f'60{idx}', 'order_id': f'60{idx}' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_successful_order)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_successful_order)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_successful_order)) if order[0] == 'buy': assert freqtrade.execute_entry(pair, amount, trade=trade) diff --git a/tests/test_integration.py b/tests/test_integration.py index 4d8b282c9..489027051 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -6,7 +6,7 @@ from freqtrade.enums import ExitCheckTuple, ExitType, TradingMode from freqtrade.persistence import Trade from freqtrade.persistence.models import Order from freqtrade.rpc.rpc import RPC -from tests.conftest import get_patched_freqtradebot, log_has_re, patch_get_signal +from tests.conftest import EXMS, get_patched_freqtradebot, log_has_re, patch_get_signal def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, @@ -56,7 +56,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, [ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]] ) cancel_order_mock = MagicMock() - mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss) + mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', stoploss) mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, @@ -239,7 +239,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: # Reduce bid amount ticker_usdt_modif = ticker_usdt.return_value ticker_usdt_modif['bid'] = ticker_usdt_modif['bid'] * 0.995 - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value=ticker_usdt_modif) + mocker.patch(f'{EXMS}.fetch_ticker', return_value=ticker_usdt_modif) # additional buy order freqtrade.process() @@ -311,7 +311,7 @@ def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None: # Reduce bid amount ticker_usdt_modif = ticker_usdt.return_value ticker_usdt_modif['ask'] = ticker_usdt_modif['ask'] * 1.004 - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value=ticker_usdt_modif) + mocker.patch(f'{EXMS}.fetch_ticker', return_value=ticker_usdt_modif) # additional buy order freqtrade.process() @@ -367,10 +367,10 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) amount_to_precision=lambda s, x, y: y, price_to_precision=lambda s, x, y: y, ) - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=False) - mocker.patch("freqtrade.exchange.Exchange.get_max_leverage", return_value=10) - mocker.patch("freqtrade.exchange.Exchange.get_funding_fees", return_value=0) - mocker.patch("freqtrade.exchange.Exchange.get_maintenance_ratio_and_amt", return_value=(0, 0)) + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False) + mocker.patch(f"{EXMS}.get_max_leverage", return_value=10) + mocker.patch(f"{EXMS}.get_funding_fees", return_value=0) + mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0, 0)) patch_get_signal(freqtrade) freqtrade.strategy.custom_entry_price = lambda **kwargs: ticker_usdt['ask'] * 0.96 @@ -413,7 +413,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) assert trade.initial_stop_loss_pct is None # Fill order - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True) + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True) freqtrade.process() trade = Trade.get_trades().first() assert len(trade.orders) == 2 @@ -428,7 +428,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) # 2nd order - not filling freqtrade.strategy.adjust_trade_position = MagicMock(return_value=120) - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=False) + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False) freqtrade.process() trade = Trade.get_trades().first() @@ -452,7 +452,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) # Fill DCA order freqtrade.strategy.adjust_trade_position = MagicMock(return_value=None) - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True) + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True) freqtrade.strategy.adjust_entry_price = MagicMock(side_effect=ValueError) freqtrade.process() @@ -484,7 +484,7 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera price_to_precision=lambda s, x, y: y, get_min_pair_stake_amount=MagicMock(return_value=10), ) - mocker.patch("freqtrade.exchange.Exchange.get_max_leverage", return_value=10) + mocker.patch(f"{EXMS}.get_max_leverage", return_value=10) patch_get_signal(freqtrade) freqtrade.strategy.leverage = MagicMock(return_value=leverage) @@ -532,8 +532,7 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera assert trade.is_open # use amount that would trunc to 0.0 once selling - mocker.patch("freqtrade.exchange.Exchange.amount_to_contract_precision", - lambda s, p, v: round(v, 1)) + mocker.patch(f"{EXMS}.amount_to_contract_precision", lambda s, p, v: round(v, 1)) freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-0.01) freqtrade.process() trade = Trade.get_trades().first() diff --git a/tests/test_worker.py b/tests/test_worker.py index 88d495e13..79e2f35d4 100644 --- a/tests/test_worker.py +++ b/tests/test_worker.py @@ -8,11 +8,11 @@ import time_machine from freqtrade.data.dataprovider import DataProvider from freqtrade.enums import State from freqtrade.worker import Worker -from tests.conftest import get_patched_worker, log_has, log_has_re +from tests.conftest import EXMS, get_patched_worker, log_has, log_has_re def test_worker_state(mocker, default_conf, markets) -> None: - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) worker = get_patched_worker(mocker, default_conf) assert worker.freqtrade.state is State.RUNNING From 2ca8b0b12e1b725c04f0ec6cdf4187dda5e17a6f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Mar 2023 20:14:15 +0100 Subject: [PATCH 133/186] Update more exchange mocks to use EXMS --- tests/commands/test_commands.py | 2 +- tests/data/test_dataprovider.py | 2 +- tests/exchange/test_exchange.py | 14 +- tests/optimize/test_backtest_detail.py | 2 +- tests/plugins/test_pairlist.py | 66 +++++----- tests/rpc/test_rpc.py | 42 +++--- tests/rpc/test_rpc_apiserver.py | 22 ++-- tests/rpc/test_rpc_telegram.py | 44 +++---- tests/test_freqtradebot.py | 174 ++++++++++++------------- tests/test_integration.py | 12 +- tests/test_wallets.py | 12 +- 11 files changed, 196 insertions(+), 196 deletions(-) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index d2ce287e9..0ba1924a7 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -953,7 +953,7 @@ def test_start_list_freqAI_models(capsys): def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys): patch_exchange(mocker, mock_markets=True) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, exchange_has=MagicMock(return_value=True), get_tickers=tickers, ) diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index ff748e976..0e10b5848 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -301,7 +301,7 @@ def test_current_whitelist(mocker, default_conf, tickers): # patch default conf to volumepairlist default_conf['pairlists'][0] = {'method': 'VolumePairList', "number_assets": 5} - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, exchange_has=MagicMock(return_value=True), get_tickers=tickers) exchange = get_patched_exchange(mocker, default_conf) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index c9d1b6cab..843195796 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1234,7 +1234,7 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill exchange_name, order_book_l2_usd, converted): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, exchange_has=MagicMock(return_value=True), fetch_l2_order_book=order_book_l2_usd, ) @@ -1296,7 +1296,7 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou exchange_name, order_book_l2_usd): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, exchange_has=MagicMock(return_value=True), fetch_l2_order_book=order_book_l2_usd, ) @@ -3238,7 +3238,7 @@ def test_fetch_order_or_stoploss_order(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, id='binance') fetch_order_mock = MagicMock() fetch_stoploss_order_mock = MagicMock() - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, fetch_order=fetch_order_mock, fetch_stoploss_order=fetch_stoploss_order_mock, ) @@ -3367,7 +3367,7 @@ def test_stoploss_order_unsupported_exchange(default_conf, mocker): def test_merge_ft_has_dict(default_conf, mocker): - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, _init_ccxt=MagicMock(return_value=MagicMock()), _load_async_markets=MagicMock(), validate_pairs=MagicMock(), @@ -3402,7 +3402,7 @@ def test_merge_ft_has_dict(default_conf, mocker): def test_get_valid_pair_combination(default_conf, mocker, markets): - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, _init_ccxt=MagicMock(return_value=MagicMock()), _load_async_markets=MagicMock(), validate_pairs=MagicMock(), @@ -3494,7 +3494,7 @@ def test_get_markets(default_conf, mocker, markets_static, spot_only, futures_only, expected_keys, test_comment # Here for debugging purposes (Not used within method) ): - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, _init_ccxt=MagicMock(return_value=MagicMock()), _load_async_markets=MagicMock(), validate_pairs=MagicMock(), @@ -5071,7 +5071,7 @@ def test_get_liquidation_price1(mocker, default_conf): ] api_mock.fetch_positions = MagicMock(return_value=positions) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, exchange_has=MagicMock(return_value=True), ) default_conf['dry_run'] = False diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 90efc40b1..ae06fca1d 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -924,7 +924,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer) mocker.patch(f"{EXMS}.get_fee", return_value=0.0) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch("freqtrade.exchange.Binance.get_max_leverage", return_value=100) + mocker.patch('freqtrade.exchange.binance.Binance.get_max_leverage', return_value=100) patch_exchange(mocker) frame = _build_backtest_dataframe(data.data) backtesting = Backtesting(default_conf) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 2a7d4ccec..40a3871d7 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -116,7 +116,7 @@ def static_pl_conf(whitelist_conf): def test_log_cached(mocker, static_pl_conf, markets, tickers): - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -180,7 +180,7 @@ def test_refresh_market_pair_not_in_whitelist(mocker, markets, static_pl_conf): def test_refresh_static_pairlist(mocker, markets, static_pl_conf): freqtrade = get_patched_freqtradebot(mocker, static_pl_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, exchange_has=MagicMock(return_value=True), markets=PropertyMock(return_value=markets), ) @@ -204,7 +204,7 @@ def test_refresh_static_pairlist_noexist(mocker, markets, static_pl_conf, pairs, static_pl_conf['exchange']['pair_whitelist'] += pairs freqtrade = get_patched_freqtradebot(mocker, static_pl_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, exchange_has=MagicMock(return_value=True), markets=PropertyMock(return_value=markets), ) @@ -221,7 +221,7 @@ def test_invalid_blacklist(mocker, markets, static_pl_conf, caplog): static_pl_conf['exchange']['pair_blacklist'] = ['*/BTC'] freqtrade = get_patched_freqtradebot(mocker, static_pl_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, exchange_has=MagicMock(return_value=True), markets=PropertyMock(return_value=markets), ) @@ -237,7 +237,7 @@ def test_remove_logs_for_pairs_already_in_blacklist(mocker, markets, static_pl_c logger = logging.getLogger(__name__) freqtrade = get_patched_freqtradebot(mocker, static_pl_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, exchange_has=MagicMock(return_value=True), markets=PropertyMock(return_value=markets), ) @@ -264,14 +264,14 @@ def test_remove_logs_for_pairs_already_in_blacklist(mocker, markets, static_pl_c def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_conf): mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_tickers=tickers, exchange_has=MagicMock(return_value=True), ) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) # Remock markets with shitcoinmarkets since get_patched_freqtradebot uses the markets fixture mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=shitcoinmarkets), ) # argument: use the whitelist dynamically by exchange-volume @@ -291,7 +291,7 @@ def test_refresh_pairlist_dynamic_2(mocker, shitcoinmarkets, tickers, whitelist_ tickers_dict = tickers() mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, exchange_has=MagicMock(return_value=True), ) # Remove caching of ticker data to emulate changing volume by the time of second call @@ -302,7 +302,7 @@ def test_refresh_pairlist_dynamic_2(mocker, shitcoinmarkets, tickers, whitelist_ freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_2) # Remock markets with shitcoinmarkets since get_patched_freqtradebot uses the markets fixture mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=shitcoinmarkets), ) @@ -320,7 +320,7 @@ def test_refresh_pairlist_dynamic_2(mocker, shitcoinmarkets, tickers, whitelist_ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, exchange_has=MagicMock(return_value=True), ) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) @@ -526,12 +526,12 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, get_tickers=tickers, markets=PropertyMock(return_value=shitcoinmarkets) ) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data), ) @@ -675,7 +675,7 @@ def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, else: freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_tickers=tickers, markets=PropertyMock(return_value=shitcoinmarkets) ) @@ -687,7 +687,7 @@ def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_data = [] mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data), ) @@ -806,7 +806,7 @@ def test_PerformanceFilter_keep_mid_order(mocker, default_conf_usdt, fee, caplog def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, get_tickers=tickers, exchange_has=MagicMock(return_value=False), ) @@ -819,7 +819,7 @@ def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None def test_pair_whitelist_not_supported_Spread(mocker, default_conf, tickers) -> None: default_conf['pairlists'] = [{'method': 'StaticPairList'}, {'method': 'SpreadFilter'}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, get_tickers=tickers, exchange_has=MagicMock(return_value=False), ) @@ -832,7 +832,7 @@ def test_pair_whitelist_not_supported_Spread(mocker, default_conf, tickers) -> N @pytest.mark.parametrize("pairlist", TESTABLE_PAIRLISTS) def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): whitelist_conf['pairlists'][0]['method'] = pairlist - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True) ) @@ -861,7 +861,7 @@ def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist, whitelist, caplog, log_message, tickers): whitelist_conf['pairlists'][0]['method'] = pairlist - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -884,7 +884,7 @@ def test__whitelist_for_active_markets_empty(mocker, whitelist_conf, pairlist, t mocker.patch(f'{EXMS}.exchange_has', return_value=True) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=None), get_tickers=tickers ) @@ -905,7 +905,7 @@ def test_volumepairlist_invalid_sortvalue(mocker, whitelist_conf): def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers): - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -925,7 +925,7 @@ def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tick default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'AgeFilter', 'min_days_listed': -1}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -941,7 +941,7 @@ def test_agefilter_max_days_lower_than_min_days(mocker, default_conf, markets, t {'method': 'AgeFilter', 'min_days_listed': 3, "max_days_listed": 2}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -956,7 +956,7 @@ def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tick default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'AgeFilter', 'min_days_listed': 99999}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -976,7 +976,7 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o ('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history, } mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers, @@ -1044,7 +1044,7 @@ def test_rangestabilityfilter_checks(mocker, default_conf, markets, tickers): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'RangeStabilityFilter', 'lookback_days': 99999}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -1074,7 +1074,7 @@ def test_rangestabilityfilter_caching(mocker, markets, default_conf, tickers, oh 'min_rate_of_change': min_rate_of_change, "max_rate_of_change": max_rate_of_change}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -1088,7 +1088,7 @@ def test_rangestabilityfilter_caching(mocker, markets, default_conf, tickers, oh ('BLK/BTC', '1d', CandleType.SPOT): ohlcv_history, } mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data), ) @@ -1109,7 +1109,7 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'SpreadFilter', 'max_spread_ratio': 0.1}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -1123,7 +1123,7 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo tickers.return_value['ETH/BTC']['ask'] = 0.0 del tickers.return_value['TKN/BTC'] del tickers.return_value['LTC/BTC'] - mocker.patch.multiple('freqtrade.exchange.Exchange', get_tickers=tickers) + mocker.patch.multiple(EXMS, get_tickers=tickers) ftbot.pairlists.refresh_pairlist() assert log_has_re(r'Removed .* invalid ticker data.*', caplog) @@ -1197,7 +1197,7 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo ]) def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig, desc_expected, exception_expected): - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True) ) @@ -1269,11 +1269,11 @@ def test_performance_filter(mocker, whitelist_conf, pairlists, pair_allowlist, o mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, allowlist_conf) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, get_tickers=tickers, markets=PropertyMock(return_value=markets) ) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), ) mocker.patch.multiple('freqtrade.persistence.Trade', @@ -1389,7 +1389,7 @@ def test_ProducerPairlist_no_emc(mocker, whitelist_conf): def test_ProducerPairlist(mocker, whitelist_conf, markets): mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), ) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 40d14212f..d368107df 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -96,7 +96,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: } mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, _dry_is_price_crossed=MagicMock(side_effect=[False, True]), @@ -197,7 +197,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -264,7 +264,7 @@ def test__rpc_timeunit_profit(default_conf_usdt, ticker, fee, limit_buy_order, limit_sell_order, markets, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, markets=PropertyMock(return_value=markets) @@ -305,7 +305,7 @@ def test__rpc_timeunit_profit(default_conf_usdt, ticker, fee, def test_rpc_trade_history(mocker, default_conf, markets, fee, is_short): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets) ) @@ -333,7 +333,7 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short): stoploss_mock = MagicMock() cancel_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets), cancel_order=cancel_mock, cancel_stoploss_order=stoploss_mock, @@ -384,7 +384,7 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=1.1) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -455,7 +455,7 @@ def test_rpc_balance_handle_error(default_conf, mocker): mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=mock_balance), get_tickers=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx')) ) @@ -518,7 +518,7 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, validate_trading_mode_and_margin_mode=MagicMock(), get_balances=MagicMock(return_value=mock_balance), fetch_positions=MagicMock(return_value=mock_pos), @@ -595,7 +595,7 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): def test_rpc_start(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock() ) @@ -616,7 +616,7 @@ def test_rpc_start(mocker, default_conf) -> None: def test_rpc_stop(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock() ) @@ -638,7 +638,7 @@ def test_rpc_stop(mocker, default_conf) -> None: def test_rpc_stopentry(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock() ) @@ -658,7 +658,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: cancel_order_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, cancel_order=cancel_order_mock, fetch_order=MagicMock( @@ -787,7 +787,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: def test_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -810,7 +810,7 @@ def test_enter_tag_performance_handle(default_conf, ticker, fee, mocker) -> None mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -842,7 +842,7 @@ def test_enter_tag_performance_handle(default_conf, ticker, fee, mocker) -> None def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets) ) @@ -871,7 +871,7 @@ def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee): def test_exit_reason_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -898,7 +898,7 @@ def test_exit_reason_performance_handle(default_conf_usdt, ticker, fee, mocker) def test_exit_reason_performance_handle_2(mocker, default_conf, markets, fee): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets) ) @@ -927,7 +927,7 @@ def test_exit_reason_performance_handle_2(mocker, default_conf, markets, fee): def test_mix_tag_performance_handle(default_conf, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -951,7 +951,7 @@ def test_mix_tag_performance_handle(default_conf, ticker, fee, mocker) -> None: def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets) ) @@ -981,7 +981,7 @@ def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee): def test_rpc_count(mocker, default_conf, ticker, fee) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -1006,7 +1006,7 @@ def test_rpc_force_entry(mocker, default_conf, ticker, fee, limit_buy_order_open mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) buy_mm = MagicMock(return_value=limit_buy_order_open) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 46cfd07d7..b104ec854 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -507,7 +507,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -594,7 +594,7 @@ def test_api_daily(botclient, mocker, ticker, fee, markets): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -613,7 +613,7 @@ def test_api_trades(botclient, mocker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets) ) rc = client_get(client, f"{BASE_URI}/trades") @@ -644,7 +644,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets), fetch_ticker=ticker, ) @@ -668,7 +668,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short): stoploss_mock = MagicMock() cancel_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets), cancel_order=cancel_mock, cancel_stoploss_order=stoploss_mock, @@ -713,7 +713,7 @@ def test_api_delete_open_order(botclient, mocker, fee, markets, ticker, is_short stoploss_mock = MagicMock() cancel_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets), fetch_ticker=ticker, cancel_order=cancel_mock, @@ -780,7 +780,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -842,7 +842,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected) ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -900,7 +900,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -981,7 +981,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -1274,7 +1274,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): def test_api_forceexit(botclient, mocker, ticker, fee, markets): ftbot, client = botclient mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 84ca1914e..3e1421cb5 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -240,7 +240,7 @@ def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None: default_conf['telegram']['chat_id'] = "123" default_conf['position_adjustment_enable'] = True mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_order=MagicMock(return_value=None), get_rate=MagicMock(return_value=0.22), ) @@ -292,7 +292,7 @@ def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None default_conf['telegram']['chat_id'] = "123" default_conf['position_adjustment_enable'] = True mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_order=MagicMock(return_value=None), get_rate=MagicMock(return_value=0.22), ) @@ -314,7 +314,7 @@ def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: default_conf['max_open_trades'] = 3 mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=True), @@ -391,7 +391,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -436,7 +436,7 @@ def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machi return_value=1.1 ) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -491,7 +491,7 @@ def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machi def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker ) @@ -525,7 +525,7 @@ def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mach return_value=1.1 ) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -595,7 +595,7 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac return_value=1.1 ) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -676,7 +676,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f limit_sell_order_usdt, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=1.1) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, ) @@ -739,7 +739,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -930,7 +930,7 @@ def test_telegram_forceexit_handle(default_conf, update, ticker, fee, patch_exchange(mocker) patch_whitelist(mocker, default_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=True), @@ -999,7 +999,7 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee, patch_whitelist(mocker, default_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=True), @@ -1015,7 +1015,7 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee, # Decrease the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_sell_down ) @@ -1070,7 +1070,7 @@ def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) patch_whitelist(mocker, default_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=True), @@ -1155,7 +1155,7 @@ def test_forceexit_handle_invalid(default_conf, update, mocker) -> None: def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None: default_conf['max_open_trades'] = 4 mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=True), @@ -1282,7 +1282,7 @@ def test_force_enter_no_pair(default_conf, update, mocker) -> None: def test_telegram_performance_handle(default_conf_usdt, update, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -1300,7 +1300,7 @@ def test_telegram_performance_handle(default_conf_usdt, update, ticker, fee, moc def test_telegram_entry_tag_performance_handle( default_conf_usdt, update, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -1331,7 +1331,7 @@ def test_telegram_entry_tag_performance_handle( def test_telegram_exit_reason_performance_handle(default_conf_usdt, update, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -1362,7 +1362,7 @@ def test_telegram_exit_reason_performance_handle(default_conf_usdt, update, tick def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -1394,7 +1394,7 @@ def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ticker, def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -1423,7 +1423,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -1685,7 +1685,7 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short): def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short, ticker): mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, ) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index a80937e1a..1ea3ebfc6 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -165,7 +165,7 @@ def test_check_available_stake_amount( patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_buy_order_usdt_open), get_fee=fee @@ -235,7 +235,7 @@ def test_edge_overrides_stoploss(limit_order, fee, caplog, mocker, 'last': enter_price, } mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value=ticker_val), get_fee=fee, ) @@ -270,7 +270,7 @@ def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) - patch_exchange(mocker) default_conf_usdt['max_open_trades'] = 2 mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=False), @@ -305,7 +305,7 @@ def test_create_trade(default_conf_usdt, ticker_usdt, limit_order, patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=False), @@ -341,7 +341,7 @@ def test_create_trade_no_stake_amount(default_conf_usdt, ticker_usdt, fee, mocke patch_exchange(mocker) patch_wallet(mocker, free=default_conf_usdt['stake_amount'] * 0.5) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, ) @@ -367,7 +367,7 @@ def test_create_trade_minimal_amount( patch_exchange(mocker) enter_mock = MagicMock(return_value=limit_order_open[entry_side(is_short)]) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=enter_mock, get_fee=fee, @@ -402,7 +402,7 @@ def test_enter_positions_no_pairs_left(default_conf_usdt, ticker_usdt, limit_buy patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_buy_order_usdt_open), get_fee=fee, @@ -429,7 +429,7 @@ def test_enter_positions_global_pairlock(default_conf_usdt, ticker_usdt, limit_b patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value={'id': limit_buy_order_usdt['id']}), get_fee=fee, @@ -480,7 +480,7 @@ def test_create_trade_no_signal(default_conf_usdt, fee, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_fee=fee, ) default_conf_usdt['stake_amount'] = 10 @@ -503,7 +503,7 @@ def test_create_trades_multiple_trades( default_conf_usdt['dry_run_wallet'] = 60.0 * max_open mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_buy_order_usdt_open), get_fee=fee, @@ -525,7 +525,7 @@ def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker, patch_exchange(mocker) default_conf_usdt['max_open_trades'] = 4 mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_buy_order_usdt_open), get_fee=fee, @@ -559,7 +559,7 @@ def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_order, lim patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_order_open[entry_side(is_short)]), fetch_order=MagicMock(return_value=limit_order[entry_side(is_short)]), @@ -595,7 +595,7 @@ def test_process_exchange_failures(default_conf_usdt, ticker_usdt, mocker) -> No patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(side_effect=TemporaryError) ) @@ -612,7 +612,7 @@ def test_process_operational_exception(default_conf_usdt, ticker_usdt, mocker) - msg_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(side_effect=OperationalException) ) @@ -631,7 +631,7 @@ def test_process_trade_handling(default_conf_usdt, ticker_usdt, limit_buy_order_ patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_buy_order_usdt_open), fetch_order=MagicMock(return_value=limit_buy_order_usdt_open), @@ -658,7 +658,7 @@ def test_process_trade_no_whitelist_pair(default_conf_usdt, ticker_usdt, limit_b patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value={'id': limit_buy_order_usdt['id']}), fetch_order=MagicMock(return_value=limit_buy_order_usdt), @@ -706,7 +706,7 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker) refresh_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(side_effect=TemporaryError), refresh_latest_ohlcv=refresh_mock, @@ -797,7 +797,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, enter_rate_mock = MagicMock(return_value=bid) enter_mm = MagicMock(return_value=open_order) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_rate=enter_rate_mock, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, @@ -946,7 +946,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, freqtrade.strategy.custom_entry_price = lambda **kwargs: None mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_rate=MagicMock(return_value=10), ) @@ -976,7 +976,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, order['id'] = '55672' mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_max_pair_stake_amount=MagicMock(return_value=500), ) freqtrade.exchange.get_max_pair_stake_amount = MagicMock(return_value=500) @@ -999,7 +999,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order, is_short) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1034,7 +1034,7 @@ def test_execute_entry_min_leverage(mocker, default_conf_usdt, fee, limit_order, default_conf_usdt['margin_mode'] = 'isolated' freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1096,7 +1096,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1250,7 +1250,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1297,7 +1297,7 @@ def test_create_stoploss_order_invalid_order( {'id': order['id']} ]) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1346,7 +1346,7 @@ def test_create_stoploss_order_insufficient_funds( mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds') mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1397,7 +1397,7 @@ def test_handle_stoploss_on_exchange_trailing( stoploss = MagicMock(return_value={'id': 13434334}) patch_RPCManager(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 2.19, 'ask': 2.2, @@ -1522,7 +1522,7 @@ def test_handle_stoploss_on_exchange_trailing_error( patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1586,8 +1586,8 @@ def test_handle_stoploss_on_exchange_trailing_error( # Fail creating stoploss order trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime caplog.clear() - cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock()) - mocker.patch("freqtrade.exchange.Binance.create_stoploss", side_effect=ExchangeError()) + cancel_mock = mocker.patch('freqtrade.exchange.binance.Binance.cancel_stoploss_order') + mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', side_effect=ExchangeError()) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) assert cancel_mock.call_count == 1 assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog) @@ -1597,7 +1597,7 @@ def test_stoploss_on_exchange_price_rounding( mocker, default_conf_usdt, fee, open_trade_usdt) -> None: patch_RPCManager(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_fee=fee, ) price_mock = MagicMock(side_effect=lambda p, s: int(s)) @@ -1630,7 +1630,7 @@ def test_handle_stoploss_on_exchange_custom_stop( stoploss = MagicMock(return_value={'id': 13434334}) patch_RPCManager(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1758,7 +1758,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde edge_conf['dry_run_wallet'] = 999.9 edge_conf['exchange']['name'] = 'binance' mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 2.19, 'ask': 2.2, @@ -2145,7 +2145,7 @@ def test_handle_trade( patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 2.19, 'ask': 2.2, @@ -2198,7 +2198,7 @@ def test_handle_overlapping_signals( patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(side_effect=[ open_order, @@ -2276,7 +2276,7 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee, patch_RPCManager(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(side_effect=[ open_order, @@ -2319,7 +2319,7 @@ def test_handle_trade_use_exit_signal( caplog.set_level(logging.DEBUG) patch_RPCManager(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(side_effect=[ enter_open_order, @@ -2359,7 +2359,7 @@ def test_close_trade( patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=open_order), get_fee=fee, @@ -2416,7 +2416,7 @@ def test_manage_open_orders_entry_usercustom( patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=old_order), cancel_order=cancel_order_mock, @@ -2477,7 +2477,7 @@ def test_manage_open_orders_entry( cancel_order_mock = MagicMock(return_value=limit_buy_cancel) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=old_order), cancel_order_with_result=cancel_order_mock, @@ -2516,7 +2516,7 @@ def test_adjust_entry_cancel( limit_buy_cancel['status'] = 'canceled' cancel_order_mock = MagicMock(return_value=limit_buy_cancel) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=old_order), cancel_order_with_result=cancel_order_mock, @@ -2557,7 +2557,7 @@ def test_adjust_entry_maintain_replace( limit_buy_cancel['status'] = 'canceled' cancel_order_mock = MagicMock(return_value=limit_buy_cancel) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=old_order), cancel_order_with_result=cancel_order_mock, @@ -2609,7 +2609,7 @@ def test_check_handle_cancelled_buy( patch_exchange(mocker) old_order.update({"status": "canceled", 'filled': 0.0}) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=old_order), cancel_order=cancel_order_mock, @@ -2639,7 +2639,7 @@ def test_manage_open_orders_buy_exception( cancel_order_mock = MagicMock() patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, validate_pairs=MagicMock(), fetch_ticker=ticker_usdt, fetch_order=MagicMock(side_effect=ExchangeError), @@ -2680,7 +2680,7 @@ def test_manage_open_orders_exit_usercustom( mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=0.0) et_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit') mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=limit_sell_order_old), cancel_order=cancel_order_mock @@ -2758,7 +2758,7 @@ def test_manage_open_orders_exit( limit_sell_order_old['side'] = 'buy' if is_short else 'sell' patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=limit_sell_order_old), cancel_order=cancel_order_mock, @@ -2800,7 +2800,7 @@ def test_check_handle_cancelled_exit( patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=limit_sell_order_old), cancel_order_with_result=cancel_order_mock @@ -2841,7 +2841,7 @@ def test_manage_open_orders_partial( cancel_order_mock = MagicMock(return_value=limit_buy_canceled) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=limit_buy_order_old_partial), cancel_order_with_result=cancel_order_mock @@ -2881,7 +2881,7 @@ def test_manage_open_orders_partial_fee( mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=0)) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=limit_buy_order_old_partial), cancel_order_with_result=cancel_order_mock, @@ -2929,7 +2929,7 @@ def test_manage_open_orders_partial_except( cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=limit_buy_order_old_partial), cancel_order_with_result=cancel_order_mock, @@ -2975,7 +2975,7 @@ def test_manage_open_orders_exception(default_conf_usdt, ticker_usdt, open_trade handle_cancel_exit=MagicMock(), ) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(side_effect=ExchangeError('Oh snap')), cancel_order=cancel_order_mock @@ -3086,7 +3086,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order l_order = limit_order[entry_side(is_short)] cancel_order_mock = MagicMock(return_value=cancelorder) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, cancel_order=cancel_order_mock, fetch_order=MagicMock(side_effect=InvalidOrderException) ) @@ -3116,7 +3116,7 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None: patch_exchange(mocker) cancel_order_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, cancel_order=cancel_order_mock, ) mocker.patch(f'{EXMS}.get_rate', return_value=0.245441) @@ -3245,7 +3245,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_ rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=False), @@ -3266,7 +3266,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_ # Increase the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_down if is_short else ticker_usdt_sell_up ) # Prevented sell ... @@ -3328,7 +3328,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=False), @@ -3346,7 +3346,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd # Decrease the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down ) freqtrade.execute_trade_exit( @@ -3397,7 +3397,7 @@ def test_execute_trade_exit_custom_exit_price( rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=False), @@ -3420,7 +3420,7 @@ def test_execute_trade_exit_custom_exit_price( # Increase the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_up ) @@ -3478,7 +3478,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=False), @@ -3496,7 +3496,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( # Decrease the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down ) @@ -3554,7 +3554,7 @@ def test_execute_trade_exit_sloe_cancel_exception( ]) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, create_order=create_order_mock, @@ -3592,7 +3592,7 @@ def test_execute_trade_exit_with_stoploss_on_exchange( cancel_order = MagicMock(return_value=True) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, amount_to_precision=lambda s, x, y: y, @@ -3619,7 +3619,7 @@ def test_execute_trade_exit_with_stoploss_on_exchange( # Increase the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_up ) @@ -3643,7 +3643,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, amount_to_precision=lambda s, x, y: y, @@ -3740,7 +3740,7 @@ def test_execute_trade_exit_market_order( rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=True), @@ -3759,7 +3759,7 @@ def test_execute_trade_exit_market_order( # Increase the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_up, _dry_is_price_crossed=MagicMock(return_value=False), ) @@ -3815,7 +3815,7 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds') mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, create_order=MagicMock(side_effect=[ @@ -3834,7 +3834,7 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u # Increase the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_up ) @@ -3869,7 +3869,7 @@ def test_exit_profit_only( patch_exchange(mocker) eside = entry_side(is_short) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': bid, 'ask': ask, @@ -3920,7 +3920,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_order, limit_order_ope patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 0.00002172, 'ask': 0.00002173, @@ -3997,7 +3997,7 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, ) @@ -4013,7 +4013,7 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, # Decrease the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_down ) @@ -4042,7 +4042,7 @@ def test_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limit_order_ patch_exchange(mocker) eside = entry_side(is_short) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 2.19, 'ask': 2.2, @@ -4093,7 +4093,7 @@ def test_trailing_stop_loss(default_conf_usdt, limit_order_open, patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 2.0, 'ask': 2.0, @@ -4163,7 +4163,7 @@ def test_trailing_stop_loss_positive( patch_exchange(mocker) eside = entry_side(is_short) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': enter_price - (-0.01 if is_short else 0.01), 'ask': enter_price - (-0.01 if is_short else 0.01), @@ -4261,7 +4261,7 @@ def test_disable_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limi patch_exchange(mocker) eside = entry_side(is_short) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 2.0, 'ask': 2.0, @@ -4738,7 +4738,7 @@ def test_order_book_depth_of_market( patch_exchange(mocker) mocker.patch(f'{EXMS}.fetch_l2_order_book', order_book_l2) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_order_open[entry_side(is_short)]), get_fee=fee, @@ -4784,7 +4784,7 @@ def test_order_book_entry_pricing1(mocker, default_conf_usdt, order_book_l2, exc patch_exchange(mocker) ticker_usdt_mock = MagicMock(return_value={'ask': ask, 'last': last}) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_l2_order_book=MagicMock(return_value=order_book) if order_book else order_book_l2, fetch_ticker=ticker_usdt_mock, ) @@ -4812,7 +4812,7 @@ def test_check_depth_of_market(default_conf_usdt, mocker, order_book_l2) -> None """ patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_l2_order_book=order_book_l2 ) default_conf_usdt['telegram']['enabled'] = False @@ -4841,7 +4841,7 @@ def test_order_book_exit_pricing( patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -4918,7 +4918,7 @@ def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_ default_conf_usdt['tradable_balance_ratio'] = 1.0 patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_buy_order_usdt_open), get_fee=fee, @@ -5430,7 +5430,7 @@ def test_update_funding_fees( mocker.patch(f'{EXMS}.refresh_latest_ohlcv', side_effect=refresh_latest_ohlcv_mock) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_rate=enter_rate_mock, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, @@ -5509,7 +5509,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: stake_amount = 10 buy_rate_mock = MagicMock(return_value=bid) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_rate=buy_rate_mock, fetch_ticker=MagicMock(return_value={ 'bid': 10, @@ -5789,7 +5789,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: amount = 100 buy_rate_mock = MagicMock(return_value=bid) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_rate=buy_rate_mock, fetch_ticker=MagicMock(return_value={ 'bid': 10, @@ -5980,7 +5980,7 @@ def test_position_adjust3(mocker, default_conf_usdt, fee, data) -> None: price = order[2] price_mock = MagicMock(return_value=price) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_rate=price_mock, fetch_ticker=MagicMock(return_value={ 'bid': 10, @@ -6066,7 +6066,7 @@ def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, ca freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) buy_rate_mock = MagicMock(return_value=10) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_rate=buy_rate_mock, fetch_ticker=MagicMock(return_value={ 'bid': 10, diff --git a/tests/test_integration.py b/tests/test_integration.py index 489027051..a3dd8d935 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -58,7 +58,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, cancel_order_mock = MagicMock() mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', stoploss) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, amount_to_precision=lambda s, x, y: y, @@ -147,7 +147,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati default_conf['telegram']['enabled'] = True mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, amount_to_precision=lambda s, x, y: y, @@ -217,7 +217,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, ) @@ -286,7 +286,7 @@ def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, amount_to_precision=lambda s, x, y: round(y, 4), @@ -361,7 +361,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, amount_to_precision=lambda s, x, y: y, @@ -477,7 +477,7 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade.trading_mode = TradingMode.FUTURES mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, amount_to_precision=lambda s, x, y: y, diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 61e8f279d..7ccc8d0f5 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -6,13 +6,13 @@ import pytest from freqtrade.constants import UNLIMITED_STAKE_AMOUNT from freqtrade.exceptions import DependencyException -from tests.conftest import create_mock_trades, get_patched_freqtradebot, patch_wallet +from tests.conftest import EXMS, create_mock_trades, get_patched_freqtradebot, patch_wallet def test_sync_wallet_at_boot(mocker, default_conf): default_conf['dry_run'] = False mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value={ "BNT": { "free": 1.0, @@ -45,7 +45,7 @@ def test_sync_wallet_at_boot(mocker, default_conf): assert 'USDT' in freqtrade.wallets._wallets assert freqtrade.wallets._last_wallet_refresh > 0 mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value={ "BNT": { "free": 1.2, @@ -87,7 +87,7 @@ def test_sync_wallet_at_boot(mocker, default_conf): def test_sync_wallet_missing_data(mocker, default_conf): default_conf['dry_run'] = False mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value={ "BNT": { "free": 1.0, @@ -136,7 +136,7 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r result1, result2, limit_buy_order_open, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, create_order=MagicMock(return_value=limit_buy_order_open), get_fee=fee @@ -312,7 +312,7 @@ def test_sync_wallet_futures_live(mocker, default_conf): } ] mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value={ "USDT": { "free": 900, From feabed30a30a13c5a69b505fecedc143f2ca9641 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Mar 2023 20:19:14 +0100 Subject: [PATCH 134/186] Update remaining exchange mock occurances --- tests/exchange/test_exchange.py | 10 +++++----- tests/exchange/test_kucoin.py | 4 ++-- tests/exchange/test_okx.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 843195796..9bc176f41 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3120,24 +3120,24 @@ def test_cancel_stoploss_order(default_conf, mocker, exchange_name): def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name): default_conf['dry_run'] = False mocker.patch(f'{EXMS}.fetch_stoploss_order', return_value={'for': 123}) - mocker.patch('freqtrade.exchange.Gate.fetch_stoploss_order', return_value={'for': 123}) + mocker.patch('freqtrade.exchange.gate.Gate.fetch_stoploss_order', return_value={'for': 123}) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) res = {'fee': {}, 'status': 'canceled', 'amount': 1234} mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value=res) - mocker.patch('freqtrade.exchange.Gate.cancel_stoploss_order', return_value=res) + mocker.patch('freqtrade.exchange.gate.Gate.cancel_stoploss_order', return_value=res) co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co == res mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value='canceled') - mocker.patch('freqtrade.exchange.Gate.cancel_stoploss_order', return_value='canceled') + mocker.patch('freqtrade.exchange.gate.Gate.cancel_stoploss_order', return_value='canceled') # Fall back to fetch_stoploss_order co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co == {'for': 123} exc = InvalidOrderException("") mocker.patch(f'{EXMS}.fetch_stoploss_order', side_effect=exc) - mocker.patch('freqtrade.exchange.Gate.fetch_stoploss_order', side_effect=exc) + mocker.patch('freqtrade.exchange.gate.Gate.fetch_stoploss_order', side_effect=exc) co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co['amount'] == 555 assert co == {'fee': {}, 'status': 'canceled', 'amount': 555, 'info': {}} @@ -3145,7 +3145,7 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name): with pytest.raises(InvalidOrderException): exc = InvalidOrderException("Did not find order") mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=exc) - mocker.patch('freqtrade.exchange.Gate.cancel_stoploss_order', side_effect=exc) + mocker.patch('freqtrade.exchange.gate.Gate.cancel_stoploss_order', side_effect=exc) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123) diff --git a/tests/exchange/test_kucoin.py b/tests/exchange/test_kucoin.py index d2cb23091..e0bb32b7c 100644 --- a/tests/exchange/test_kucoin.py +++ b/tests/exchange/test_kucoin.py @@ -146,8 +146,8 @@ def test_kucoin_create_order(default_conf, mocker, side, ordertype, rate): 'amount': 1 }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id='kucoin') exchange._set_leverage = MagicMock() exchange.set_margin_mode = MagicMock() diff --git a/tests/exchange/test_okx.py b/tests/exchange/test_okx.py index 46b1852a0..fce77f4c7 100644 --- a/tests/exchange/test_okx.py +++ b/tests/exchange/test_okx.py @@ -46,7 +46,7 @@ def test_get_maintenance_ratio_and_amt_okx( default_conf['margin_mode'] = 'isolated' default_conf['dry_run'] = False mocker.patch.multiple( - 'freqtrade.exchange.Okx', + 'freqtrade.exchange.okx.Okx', exchange_has=MagicMock(return_value=True), load_leverage_tiers=MagicMock(return_value={ 'ETH/USDT:USDT': [ From a629d455fb534510d06551be661846daf75dd117 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 08:50:57 +0000 Subject: [PATCH 135/186] Bump sqlalchemy from 1.4.46 to 2.0.3 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.4.46 to 2.0.3. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ea0e8ecb4..3e6c66757 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pandas-ta==0.3.14b ccxt==2.8.54 cryptography==39.0.1 aiohttp==3.8.4 -SQLAlchemy==1.4.46 +SQLAlchemy==2.0.3 python-telegram-bot==13.15 arrow==1.2.3 cachetools==4.2.2 From a553a9923ad57feda53b911fac918bae1e0516ef Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Feb 2023 08:51:00 +0000 Subject: [PATCH 136/186] Update types for pairlock --- freqtrade/persistence/pairlock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py index 938cd14bc..b8af1421f 100644 --- a/freqtrade/persistence/pairlock.py +++ b/freqtrade/persistence/pairlock.py @@ -21,9 +21,9 @@ class PairLock(_DECL_BASE): side = Column(String(25), nullable=False, default="*") reason = Column(String(255), nullable=True) # Time the pair was locked (start time) - lock_time = Column(DateTime(), nullable=False) + lock_time: datetime = Column(DateTime(), nullable=False) # Time until the pair is locked (end time) - lock_end_time = Column(DateTime(), nullable=False, index=True) + lock_end_time: datetime = Column(DateTime(), nullable=False, index=True) active = Column(Boolean, nullable=False, default=True, index=True) From b62830031f0f70689d985d2150343fe6a6249695 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Feb 2023 09:56:42 +0000 Subject: [PATCH 137/186] Dummy-type query objects --- freqtrade/persistence/pairlock.py | 2 ++ freqtrade/persistence/trade_model.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py index b8af1421f..2c4cfa3b4 100644 --- a/freqtrade/persistence/pairlock.py +++ b/freqtrade/persistence/pairlock.py @@ -13,6 +13,8 @@ class PairLock(_DECL_BASE): Pair Locks database model. """ __tablename__ = 'pairlocks' + # TODO: Properly type query. + query: Any id = Column(Integer, primary_key=True) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index c84fcec9e..e6f4f5b08 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -36,6 +36,8 @@ class Order(_DECL_BASE): Mirrors CCXT Order structure """ __tablename__ = 'orders' + # TODO: Properly type query. + query: Any # Uniqueness should be ensured over pair, order_id # its likely that order_id is unique per Pair on some exchanges. __table_args__ = (UniqueConstraint('ft_pair', 'order_id', name="_order_pair_order_id"),) @@ -1167,6 +1169,9 @@ class Trade(_DECL_BASE, LocalTrade): Note: Fields must be aligned with LocalTrade class """ __tablename__ = 'trades' + # TODO: Type query type throughout. + query: Any + _session: Any = None use_db: bool = True From 829e10ff87907bbcb0495c75e88a1e4e462a798f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Feb 2023 10:04:09 +0000 Subject: [PATCH 138/186] Improve Type for models.py --- freqtrade/persistence/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 7f851322e..38dbf212f 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -2,6 +2,7 @@ This module contains the class to persist trades into SQLite """ import logging +from typing import Any, Dict from sqlalchemy import create_engine, inspect from sqlalchemy.exc import NoSuchModuleError @@ -29,7 +30,7 @@ def init_db(db_url: str) -> None: :param db_url: Database to use :return: None """ - kwargs = {} + kwargs: Dict[str, Any] = {} if db_url == 'sqlite:///': raise OperationalException( From 9d455f58b1d643974add4b92177562a6312c77ad Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Feb 2023 10:04:36 +0000 Subject: [PATCH 139/186] Improve some trade model Types --- freqtrade/persistence/trade_model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index e6f4f5b08..c0019eaf2 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -1386,7 +1386,7 @@ class Trade(_DECL_BASE, LocalTrade): Returns List of dicts containing all Trades, including profit and trade count NOTE: Not supported in Backtesting. """ - filters = [Trade.is_open.is_(False)] + filters: List = [Trade.is_open.is_(False)] if minutes: start_date = datetime.now(timezone.utc) - timedelta(minutes=minutes) filters.append(Trade.close_date >= start_date) @@ -1419,7 +1419,7 @@ class Trade(_DECL_BASE, LocalTrade): NOTE: Not supported in Backtesting. """ - filters = [Trade.is_open.is_(False)] + filters: List = [Trade.is_open.is_(False)] if (pair is not None): filters.append(Trade.pair == pair) @@ -1452,7 +1452,7 @@ class Trade(_DECL_BASE, LocalTrade): NOTE: Not supported in Backtesting. """ - filters = [Trade.is_open.is_(False)] + filters: List = [Trade.is_open.is_(False)] if (pair is not None): filters.append(Trade.pair == pair) @@ -1485,7 +1485,7 @@ class Trade(_DECL_BASE, LocalTrade): NOTE: Not supported in Backtesting. """ - filters = [Trade.is_open.is_(False)] + filters: List = [Trade.is_open.is_(False)] if (pair is not None): filters.append(Trade.pair == pair) From 3a9d83f86c79ccdd90a2ad250dc2af47e7e9868f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Feb 2023 10:05:04 +0000 Subject: [PATCH 140/186] Mypy: define sqlalchemy plugin --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 6f9e5205c..71687961d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,9 @@ warn_unused_ignores = true exclude = [ '^build_helpers\.py$' ] +plugins = [ + "sqlalchemy.ext.mypy.plugin" +] [[tool.mypy.overrides]] module = "tests.*" From 41e27ba62198c228a2e06dafeb6822381ec54030 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Feb 2023 10:35:09 +0000 Subject: [PATCH 141/186] Enhance some type info --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 633e9dc71..ceb078669 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -633,7 +633,7 @@ class FreqtradeBot(LoggingMixin): return remaining = (trade.amount - amount) * current_exit_rate - if remaining < min_exit_stake: + if min_exit_stake and remaining < min_exit_stake: logger.info(f"Remaining amount of {remaining} would be smaller " f"than the minimum of {min_exit_stake}.") return @@ -1694,7 +1694,7 @@ class FreqtradeBot(LoggingMixin): raise DependencyException( f"Order_obj not found for {order_id}. This should not have happened.") - profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested + profit_rate: float = trade.close_rate if trade.close_rate else trade.close_rate_requested profit_trade = trade.calc_profit(rate=profit_rate) current_rate = self.exchange.get_rate( trade.pair, side='exit', is_short=trade.is_short, refresh=False) From 3c019e0e16c64a6d488baa056f3f117c51ee3e24 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Feb 2023 10:35:18 +0000 Subject: [PATCH 142/186] tentative augmented typing of Trade object --- freqtrade/freqtradebot.py | 3 +- freqtrade/persistence/trade_model.py | 72 ++++++++++++++-------------- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ceb078669..adc630036 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1737,7 +1737,8 @@ class FreqtradeBot(LoggingMixin): # def update_trade_state( - self, trade: Trade, order_id: str, action_order: Optional[Dict[str, Any]] = None, + self, trade: Trade, order_id: Optional[str], + action_order: Optional[Dict[str, Any]] = None, stoploss_order: bool = False, send_msg: bool = True) -> bool: """ Checks trades with open orders and updates the amount if necessary diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index c0019eaf2..9355d2a84 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -45,34 +45,34 @@ class Order(_DECL_BASE): id = Column(Integer, primary_key=True) ft_trade_id = Column(Integer, ForeignKey('trades.id'), index=True) - trade = relationship("Trade", back_populates="orders") + trade: List["Trade"] = relationship("Trade", back_populates="orders") # order_side can only be 'buy', 'sell' or 'stoploss' - ft_order_side = Column(String(25), nullable=False) - ft_pair = Column(String(25), nullable=False) - ft_is_open = Column(Boolean, nullable=False, default=True, index=True) - ft_amount = Column(Float(), nullable=False) - ft_price = Column(Float(), nullable=False) + ft_order_side: str = Column(String(25), nullable=False) + ft_pair: str = Column(String(25), nullable=False) + ft_is_open: bool = Column(Boolean, nullable=False, default=True, index=True) + ft_amount: float = Column(Float(), nullable=False) + ft_price: float = Column(Float(), nullable=False) order_id = Column(String(255), nullable=False, index=True) status = Column(String(255), nullable=True) symbol = Column(String(25), nullable=True) - order_type = Column(String(50), nullable=True) + # TODO: type: order_type type is Optional[str] + order_type: str = Column(String(50), nullable=True) side = Column(String(25), nullable=True) - price = Column(Float(), nullable=True) - average = Column(Float(), nullable=True) - amount = Column(Float(), nullable=True) - filled = Column(Float(), nullable=True) - remaining = Column(Float(), nullable=True) - cost = Column(Float(), nullable=True) - stop_price = Column(Float(), nullable=True) - order_date = Column(DateTime(), nullable=True, default=datetime.utcnow) + price: Optional[float] = Column(Float(), nullable=True) + average: Optional[float] = Column(Float(), nullable=True) + amount: Optional[float] = Column(Float(), nullable=True) + filled: Optional[float] = Column(Float(), nullable=True) + remaining: Optional[float] = Column(Float(), nullable=True) + cost: Optional[float] = Column(Float(), nullable=True) + stop_price: Optional[float] = Column(Float(), nullable=True) + order_date: datetime = Column(DateTime(), nullable=True, default=datetime.utcnow) order_filled_date = Column(DateTime(), nullable=True) order_update_date = Column(DateTime(), nullable=True) + funding_fee: Optional[float] = Column(Float(), nullable=True) - funding_fee = Column(Float(), nullable=True) - - ft_fee_base = Column(Float(), nullable=True) + ft_fee_base: Optional[float] = Column(Float(), nullable=True) @property def order_date_utc(self) -> datetime: @@ -1175,13 +1175,13 @@ class Trade(_DECL_BASE, LocalTrade): use_db: bool = True - id = Column(Integer, primary_key=True) + id: int = Column(Integer, primary_key=True) - orders = relationship("Order", order_by="Order.id", cascade="all, delete-orphan", - lazy="selectin", innerjoin=True) + orders: List[Order] = relationship("Order", order_by="Order.id", cascade="all, delete-orphan", + lazy="selectin", innerjoin=True) - exchange = Column(String(25), nullable=False) - pair = Column(String(25), nullable=False, index=True) + exchange: str = Column(String(25), nullable=False) + pair: str = Column(String(25), nullable=False, index=True) base_currency = Column(String(25), nullable=True) stake_currency = Column(String(25), nullable=True) is_open = Column(Boolean, nullable=False, default=True, index=True) @@ -1192,21 +1192,23 @@ class Trade(_DECL_BASE, LocalTrade): fee_close_cost = Column(Float(), nullable=True) fee_close_currency = Column(String(25), nullable=True) open_rate: float = Column(Float()) - open_rate_requested = Column(Float()) + open_rate_requested: float = Column(Float()) # open_trade_value - calculated via _calc_open_trade_value open_trade_value = Column(Float()) close_rate: Optional[float] = Column(Float()) - close_rate_requested = Column(Float()) - realized_profit = Column(Float(), default=0.0) + close_rate_requested: Optional[float] = Column(Float()) + # TODO: is the below type really correct? + realized_profit: float = Column(Float(), default=0.0) close_profit = Column(Float()) - close_profit_abs = Column(Float()) - stake_amount = Column(Float(), nullable=False) - max_stake_amount = Column(Float()) - amount = Column(Float()) - amount_requested = Column(Float()) + close_profit_abs: Optional[float] = Column(Float()) + stake_amount: float = Column(Float(), nullable=False) + max_stake_amount: Optional[float] = Column(Float()) + amount: float = Column(Float()) + amount_requested: Optional[float] = Column(Float()) open_date = Column(DateTime(), nullable=False, default=datetime.utcnow) close_date = Column(DateTime()) - open_order_id = Column(String(255)) + # TODO: open_order_id type should be Optional[str] + open_order_id: str = Column(String(255)) # absolute value of the stop loss stop_loss = Column(Float(), nullable=True, default=0.0) # percentage value of the stop loss @@ -1236,15 +1238,15 @@ class Trade(_DECL_BASE, LocalTrade): contract_size = Column(Float(), nullable=True) # Leverage trading properties - leverage = Column(Float(), nullable=True, default=1.0) - is_short = Column(Boolean, nullable=False, default=False) + leverage: float = Column(Float(), nullable=True, default=1.0) + is_short: bool = Column(Boolean, nullable=False, default=False) liquidation_price = Column(Float(), nullable=True) # Margin Trading Properties interest_rate = Column(Float(), nullable=False, default=0.0) # Futures properties - funding_fees = Column(Float(), nullable=True, default=None) + funding_fees: Optional[float] = Column(Float(), nullable=True, default=None) def __init__(self, **kwargs): super().__init__(**kwargs) From 39a658eac22a70726d4b7bfa8139ee423325731e Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 16 Feb 2023 07:04:45 +0000 Subject: [PATCH 143/186] Update DeclarativeBase --- freqtrade/persistence/base.py | 7 +++---- freqtrade/persistence/models.py | 6 +++--- freqtrade/persistence/pairlock.py | 4 ++-- freqtrade/persistence/trade_model.py | 6 +++--- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/freqtrade/persistence/base.py b/freqtrade/persistence/base.py index fb2d561e1..2fed715d7 100644 --- a/freqtrade/persistence/base.py +++ b/freqtrade/persistence/base.py @@ -1,7 +1,6 @@ -from typing import Any - -from sqlalchemy.orm import declarative_base +from sqlalchemy.orm import DeclarativeBase -_DECL_BASE: Any = declarative_base() +class ModelBase(DeclarativeBase): + pass diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 38dbf212f..9f90f8a74 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -10,7 +10,7 @@ from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.pool import StaticPool from freqtrade.exceptions import OperationalException -from freqtrade.persistence.base import _DECL_BASE +from freqtrade.persistence.base import ModelBase from freqtrade.persistence.migrations import check_migrate from freqtrade.persistence.pairlock import PairLock from freqtrade.persistence.trade_model import Order, Trade @@ -60,5 +60,5 @@ def init_db(db_url: str) -> None: PairLock.query = Trade._session.query_property() previous_tables = inspect(engine).get_table_names() - _DECL_BASE.metadata.create_all(engine) - check_migrate(engine, decl_base=_DECL_BASE, previous_tables=previous_tables) + ModelBase.metadata.create_all(engine) + check_migrate(engine, decl_base=ModelBase, previous_tables=previous_tables) diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py index 2c4cfa3b4..e1f659b0a 100644 --- a/freqtrade/persistence/pairlock.py +++ b/freqtrade/persistence/pairlock.py @@ -5,10 +5,10 @@ from sqlalchemy import Boolean, Column, DateTime, Integer, String, or_ from sqlalchemy.orm import Query from freqtrade.constants import DATETIME_PRINT_FORMAT -from freqtrade.persistence.base import _DECL_BASE +from freqtrade.persistence.base import ModelBase -class PairLock(_DECL_BASE): +class PairLock(ModelBase): """ Pair Locks database model. """ diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 9355d2a84..ebec36e81 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -17,14 +17,14 @@ from freqtrade.enums import ExitType, TradingMode from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange import amount_to_contract_precision, price_to_precision from freqtrade.leverage import interest -from freqtrade.persistence.base import _DECL_BASE +from freqtrade.persistence.base import ModelBase from freqtrade.util import FtPrecise logger = logging.getLogger(__name__) -class Order(_DECL_BASE): +class Order(ModelBase): """ Order database model Keeps a record of all orders placed on the exchange @@ -1161,7 +1161,7 @@ class LocalTrade(): logger.info(f"New stoploss: {trade.stop_loss}.") -class Trade(_DECL_BASE, LocalTrade): +class Trade(ModelBase, LocalTrade): """ Trade database model. Also handles updating and querying trades From 0bd9b00132623db0499325545ef210ed0512e5c5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 16 Feb 2023 07:05:57 +0000 Subject: [PATCH 144/186] Pairlock to mappedColumn --- freqtrade/persistence/pairlock.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py index e1f659b0a..b721cceac 100644 --- a/freqtrade/persistence/pairlock.py +++ b/freqtrade/persistence/pairlock.py @@ -1,8 +1,8 @@ from datetime import datetime, timezone from typing import Any, Dict, Optional -from sqlalchemy import Boolean, Column, DateTime, Integer, String, or_ -from sqlalchemy.orm import Query +from sqlalchemy import Boolean, DateTime, Integer, String, or_ +from sqlalchemy.orm import Query, mapped_column from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.persistence.base import ModelBase @@ -16,18 +16,18 @@ class PairLock(ModelBase): # TODO: Properly type query. query: Any - id = Column(Integer, primary_key=True) + id = mapped_column(Integer, primary_key=True) - pair = Column(String(25), nullable=False, index=True) + pair = mapped_column(String(25), nullable=False, index=True) # lock direction - long, short or * (for both) - side = Column(String(25), nullable=False, default="*") - reason = Column(String(255), nullable=True) + side = mapped_column(String(25), nullable=False, default="*") + reason = mapped_column(String(255), nullable=True) # Time the pair was locked (start time) - lock_time: datetime = Column(DateTime(), nullable=False) + lock_time: datetime = mapped_column(DateTime(), nullable=False) # Time until the pair is locked (end time) - lock_end_time: datetime = Column(DateTime(), nullable=False, index=True) + lock_end_time: datetime = mapped_column(DateTime(), nullable=False, index=True) - active = Column(Boolean, nullable=False, default=True, index=True) + active = mapped_column(Boolean, nullable=False, default=True, index=True) def __repr__(self): lock_time = self.lock_time.strftime(DATETIME_PRINT_FORMAT) From 98791752a9c39827385bd0c20509f560bbff88d4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 16 Feb 2023 07:07:08 +0000 Subject: [PATCH 145/186] Update TradeModels to mapped_column --- freqtrade/persistence/trade_model.py | 152 +++++++++++++-------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index ebec36e81..e8f78661e 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -7,9 +7,9 @@ from datetime import datetime, timedelta, timezone from math import isclose from typing import Any, Dict, List, Optional -from sqlalchemy import (Boolean, Column, DateTime, Enum, Float, ForeignKey, Integer, String, +from sqlalchemy import (Boolean, DateTime, Enum, Float, ForeignKey, Integer, String, UniqueConstraint, desc, func) -from sqlalchemy.orm import Query, lazyload, relationship +from sqlalchemy.orm import Query, lazyload, mapped_column, relationship from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPEN_EXCHANGE_STATES, BuySell, LongShort) @@ -42,37 +42,37 @@ class Order(ModelBase): # its likely that order_id is unique per Pair on some exchanges. __table_args__ = (UniqueConstraint('ft_pair', 'order_id', name="_order_pair_order_id"),) - id = Column(Integer, primary_key=True) - ft_trade_id = Column(Integer, ForeignKey('trades.id'), index=True) + id = mapped_column(Integer, primary_key=True) + ft_trade_id = mapped_column(Integer, ForeignKey('trades.id'), index=True) trade: List["Trade"] = relationship("Trade", back_populates="orders") # order_side can only be 'buy', 'sell' or 'stoploss' - ft_order_side: str = Column(String(25), nullable=False) - ft_pair: str = Column(String(25), nullable=False) - ft_is_open: bool = Column(Boolean, nullable=False, default=True, index=True) - ft_amount: float = Column(Float(), nullable=False) - ft_price: float = Column(Float(), nullable=False) + ft_order_side: str = mapped_column(String(25), nullable=False) + ft_pair: str = mapped_column(String(25), nullable=False) + ft_is_open: bool = mapped_column(Boolean, nullable=False, default=True, index=True) + ft_amount: float = mapped_column(Float(), nullable=False) + ft_price: float = mapped_column(Float(), nullable=False) - order_id = Column(String(255), nullable=False, index=True) - status = Column(String(255), nullable=True) - symbol = Column(String(25), nullable=True) + order_id = mapped_column(String(255), nullable=False, index=True) + status = mapped_column(String(255), nullable=True) + symbol = mapped_column(String(25), nullable=True) # TODO: type: order_type type is Optional[str] - order_type: str = Column(String(50), nullable=True) - side = Column(String(25), nullable=True) - price: Optional[float] = Column(Float(), nullable=True) - average: Optional[float] = Column(Float(), nullable=True) - amount: Optional[float] = Column(Float(), nullable=True) - filled: Optional[float] = Column(Float(), nullable=True) - remaining: Optional[float] = Column(Float(), nullable=True) - cost: Optional[float] = Column(Float(), nullable=True) - stop_price: Optional[float] = Column(Float(), nullable=True) - order_date: datetime = Column(DateTime(), nullable=True, default=datetime.utcnow) - order_filled_date = Column(DateTime(), nullable=True) - order_update_date = Column(DateTime(), nullable=True) - funding_fee: Optional[float] = Column(Float(), nullable=True) + order_type: str = mapped_column(String(50), nullable=True) + side = mapped_column(String(25), nullable=True) + price: Optional[float] = mapped_column(Float(), nullable=True) + average: Optional[float] = mapped_column(Float(), nullable=True) + amount: Optional[float] = mapped_column(Float(), nullable=True) + filled: Optional[float] = mapped_column(Float(), nullable=True) + remaining: Optional[float] = mapped_column(Float(), nullable=True) + cost: Optional[float] = mapped_column(Float(), nullable=True) + stop_price: Optional[float] = mapped_column(Float(), nullable=True) + order_date: datetime = mapped_column(DateTime(), nullable=True, default=datetime.utcnow) + order_filled_date = mapped_column(DateTime(), nullable=True) + order_update_date = mapped_column(DateTime(), nullable=True) + funding_fee: Optional[float] = mapped_column(Float(), nullable=True) - ft_fee_base: Optional[float] = Column(Float(), nullable=True) + ft_fee_base: Optional[float] = mapped_column(Float(), nullable=True) @property def order_date_utc(self) -> datetime: @@ -1175,78 +1175,78 @@ class Trade(ModelBase, LocalTrade): use_db: bool = True - id: int = Column(Integer, primary_key=True) + id: int = mapped_column(Integer, primary_key=True) orders: List[Order] = relationship("Order", order_by="Order.id", cascade="all, delete-orphan", lazy="selectin", innerjoin=True) - exchange: str = Column(String(25), nullable=False) - pair: str = Column(String(25), nullable=False, index=True) - base_currency = Column(String(25), nullable=True) - stake_currency = Column(String(25), nullable=True) - is_open = Column(Boolean, nullable=False, default=True, index=True) - fee_open = Column(Float(), nullable=False, default=0.0) - fee_open_cost = Column(Float(), nullable=True) - fee_open_currency = Column(String(25), nullable=True) - fee_close = Column(Float(), nullable=False, default=0.0) - fee_close_cost = Column(Float(), nullable=True) - fee_close_currency = Column(String(25), nullable=True) - open_rate: float = Column(Float()) - open_rate_requested: float = Column(Float()) + exchange: str = mapped_column(String(25), nullable=False) + pair: str = mapped_column(String(25), nullable=False, index=True) + base_currency = mapped_column(String(25), nullable=True) + stake_currency = mapped_column(String(25), nullable=True) + is_open = mapped_column(Boolean, nullable=False, default=True, index=True) + fee_open = mapped_column(Float(), nullable=False, default=0.0) + fee_open_cost = mapped_column(Float(), nullable=True) + fee_open_currency = mapped_column(String(25), nullable=True) + fee_close = mapped_column(Float(), nullable=False, default=0.0) + fee_close_cost = mapped_column(Float(), nullable=True) + fee_close_currency = mapped_column(String(25), nullable=True) + open_rate: float = mapped_column(Float()) + open_rate_requested: float = mapped_column(Float()) # open_trade_value - calculated via _calc_open_trade_value - open_trade_value = Column(Float()) - close_rate: Optional[float] = Column(Float()) - close_rate_requested: Optional[float] = Column(Float()) + open_trade_value = mapped_column(Float()) + close_rate: Optional[float] = mapped_column(Float()) + close_rate_requested: Optional[float] = mapped_column(Float()) # TODO: is the below type really correct? - realized_profit: float = Column(Float(), default=0.0) - close_profit = Column(Float()) - close_profit_abs: Optional[float] = Column(Float()) - stake_amount: float = Column(Float(), nullable=False) - max_stake_amount: Optional[float] = Column(Float()) - amount: float = Column(Float()) - amount_requested: Optional[float] = Column(Float()) - open_date = Column(DateTime(), nullable=False, default=datetime.utcnow) - close_date = Column(DateTime()) + realized_profit: float = mapped_column(Float(), default=0.0) + close_profit = mapped_column(Float()) + close_profit_abs: Optional[float] = mapped_column(Float()) + stake_amount: float = mapped_column(Float(), nullable=False) + max_stake_amount: Optional[float] = mapped_column(Float()) + amount: float = mapped_column(Float()) + amount_requested: Optional[float] = mapped_column(Float()) + open_date = mapped_column(DateTime(), nullable=False, default=datetime.utcnow) + close_date = mapped_column(DateTime()) # TODO: open_order_id type should be Optional[str] - open_order_id: str = Column(String(255)) + open_order_id: str = mapped_column(String(255)) # absolute value of the stop loss - stop_loss = Column(Float(), nullable=True, default=0.0) + stop_loss = mapped_column(Float(), nullable=True, default=0.0) # percentage value of the stop loss - stop_loss_pct = Column(Float(), nullable=True) + stop_loss_pct = mapped_column(Float(), nullable=True) # absolute value of the initial stop loss - initial_stop_loss = Column(Float(), nullable=True, default=0.0) + initial_stop_loss = mapped_column(Float(), nullable=True, default=0.0) # percentage value of the initial stop loss - initial_stop_loss_pct = Column(Float(), nullable=True) + initial_stop_loss_pct = mapped_column(Float(), nullable=True) # stoploss order id which is on exchange - stoploss_order_id = Column(String(255), nullable=True, index=True) + stoploss_order_id = mapped_column(String(255), nullable=True, index=True) # last update time of the stoploss order on exchange - stoploss_last_update = Column(DateTime(), nullable=True) + stoploss_last_update = mapped_column(DateTime(), nullable=True) # absolute value of the highest reached price - max_rate = Column(Float(), nullable=True, default=0.0) + max_rate = mapped_column(Float(), nullable=True, default=0.0) # Lowest price reached - min_rate = Column(Float(), nullable=True) - exit_reason = Column(String(100), nullable=True) - exit_order_status = Column(String(100), nullable=True) - strategy = Column(String(100), nullable=True) - enter_tag = Column(String(100), nullable=True) - timeframe = Column(Integer, nullable=True) + min_rate = mapped_column(Float(), nullable=True) + exit_reason = mapped_column(String(100), nullable=True) + exit_order_status = mapped_column(String(100), nullable=True) + strategy = mapped_column(String(100), nullable=True) + enter_tag = mapped_column(String(100), nullable=True) + timeframe = mapped_column(Integer, nullable=True) - trading_mode = Column(Enum(TradingMode), nullable=True) - amount_precision = Column(Float(), nullable=True) - price_precision = Column(Float(), nullable=True) - precision_mode = Column(Integer, nullable=True) - contract_size = Column(Float(), nullable=True) + trading_mode = mapped_column(Enum(TradingMode), nullable=True) + amount_precision = mapped_column(Float(), nullable=True) + price_precision = mapped_column(Float(), nullable=True) + precision_mode = mapped_column(Integer, nullable=True) + contract_size = mapped_column(Float(), nullable=True) # Leverage trading properties - leverage: float = Column(Float(), nullable=True, default=1.0) - is_short: bool = Column(Boolean, nullable=False, default=False) - liquidation_price = Column(Float(), nullable=True) + leverage: float = mapped_column(Float(), nullable=True, default=1.0) + is_short: bool = mapped_column(Boolean, nullable=False, default=False) + liquidation_price = mapped_column(Float(), nullable=True) # Margin Trading Properties - interest_rate = Column(Float(), nullable=False, default=0.0) + interest_rate = mapped_column(Float(), nullable=False, default=0.0) # Futures properties - funding_fees: Optional[float] = Column(Float(), nullable=True, default=None) + funding_fees: Optional[float] = mapped_column(Float(), nullable=True, default=None) def __init__(self, **kwargs): super().__init__(**kwargs) From 13b1a3e737689effd4dc14dcc1a60abda309375b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 16 Feb 2023 07:14:07 +0000 Subject: [PATCH 146/186] Properly pairlock columns using mapped --- freqtrade/persistence/pairlock.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py index b721cceac..2c81beb85 100644 --- a/freqtrade/persistence/pairlock.py +++ b/freqtrade/persistence/pairlock.py @@ -1,8 +1,8 @@ from datetime import datetime, timezone from typing import Any, Dict, Optional -from sqlalchemy import Boolean, DateTime, Integer, String, or_ -from sqlalchemy.orm import Query, mapped_column +from sqlalchemy import String, or_ +from sqlalchemy.orm import Mapped, Query, mapped_column from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.persistence.base import ModelBase @@ -16,20 +16,20 @@ class PairLock(ModelBase): # TODO: Properly type query. query: Any - id = mapped_column(Integer, primary_key=True) + id: Mapped[int] = mapped_column(primary_key=True) - pair = mapped_column(String(25), nullable=False, index=True) + pair: Mapped[str] = mapped_column(String(25), nullable=False, index=True) # lock direction - long, short or * (for both) - side = mapped_column(String(25), nullable=False, default="*") - reason = mapped_column(String(255), nullable=True) + side: Mapped[str] = mapped_column(String(25), nullable=False, default="*") + reason: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) # Time the pair was locked (start time) - lock_time: datetime = mapped_column(DateTime(), nullable=False) + lock_time: Mapped[datetime] = mapped_column(nullable=False) # Time until the pair is locked (end time) - lock_end_time: datetime = mapped_column(DateTime(), nullable=False, index=True) + lock_end_time: Mapped[datetime] = mapped_column(nullable=False, index=True) - active = mapped_column(Boolean, nullable=False, default=True, index=True) + active: Mapped[bool] = mapped_column(nullable=False, default=True, index=True) - def __repr__(self): + def __repr__(self) -> str: lock_time = self.lock_time.strftime(DATETIME_PRINT_FORMAT) lock_end_time = self.lock_end_time.strftime(DATETIME_PRINT_FORMAT) return ( From bb116456a957defcb5fe422b91094cd7dd640a51 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 16 Feb 2023 07:17:56 +0000 Subject: [PATCH 147/186] Update Types for Order object --- freqtrade/persistence/trade_model.py | 52 ++++++++++++++-------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index e8f78661e..c4bfc7b1f 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -9,7 +9,7 @@ from typing import Any, Dict, List, Optional from sqlalchemy import (Boolean, DateTime, Enum, Float, ForeignKey, Integer, String, UniqueConstraint, desc, func) -from sqlalchemy.orm import Query, lazyload, mapped_column, relationship +from sqlalchemy.orm import Mapped, Query, lazyload, mapped_column, relationship from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPEN_EXCHANGE_STATES, BuySell, LongShort) @@ -42,37 +42,37 @@ class Order(ModelBase): # its likely that order_id is unique per Pair on some exchanges. __table_args__ = (UniqueConstraint('ft_pair', 'order_id', name="_order_pair_order_id"),) - id = mapped_column(Integer, primary_key=True) - ft_trade_id = mapped_column(Integer, ForeignKey('trades.id'), index=True) + id: Mapped[int] = mapped_column(Integer, primary_key=True) + ft_trade_id: Mapped[int] = mapped_column(Integer, ForeignKey('trades.id'), index=True) - trade: List["Trade"] = relationship("Trade", back_populates="orders") + trade: Mapped[List["Trade"]] = relationship("Trade", back_populates="orders") # order_side can only be 'buy', 'sell' or 'stoploss' - ft_order_side: str = mapped_column(String(25), nullable=False) - ft_pair: str = mapped_column(String(25), nullable=False) - ft_is_open: bool = mapped_column(Boolean, nullable=False, default=True, index=True) - ft_amount: float = mapped_column(Float(), nullable=False) - ft_price: float = mapped_column(Float(), nullable=False) + ft_order_side: Mapped[str] = mapped_column(String(25), nullable=False) + ft_pair: Mapped[str] = mapped_column(String(25), nullable=False) + ft_is_open: Mapped[bool] = mapped_column(nullable=False, default=True, index=True) + ft_amount: Mapped[float] = mapped_column(Float(), nullable=False) + ft_price: Mapped[float] = mapped_column(Float(), nullable=False) - order_id = mapped_column(String(255), nullable=False, index=True) - status = mapped_column(String(255), nullable=True) - symbol = mapped_column(String(25), nullable=True) + order_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) + status: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + symbol: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) # TODO: type: order_type type is Optional[str] - order_type: str = mapped_column(String(50), nullable=True) - side = mapped_column(String(25), nullable=True) - price: Optional[float] = mapped_column(Float(), nullable=True) - average: Optional[float] = mapped_column(Float(), nullable=True) - amount: Optional[float] = mapped_column(Float(), nullable=True) - filled: Optional[float] = mapped_column(Float(), nullable=True) - remaining: Optional[float] = mapped_column(Float(), nullable=True) - cost: Optional[float] = mapped_column(Float(), nullable=True) - stop_price: Optional[float] = mapped_column(Float(), nullable=True) - order_date: datetime = mapped_column(DateTime(), nullable=True, default=datetime.utcnow) - order_filled_date = mapped_column(DateTime(), nullable=True) - order_update_date = mapped_column(DateTime(), nullable=True) - funding_fee: Optional[float] = mapped_column(Float(), nullable=True) + order_type: Mapped[str] = mapped_column(String(50), nullable=True) + side: Mapped[str] = mapped_column(String(25), nullable=True) + price: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + average: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + amount: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + filled: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + remaining: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + stop_price: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + order_date: Mapped[datetime] = mapped_column(nullable=True, default=datetime.utcnow) + order_filled_date: Mapped[Optional[datetime]] = mapped_column(nullable=True) + order_update_date: Mapped[Optional[datetime]] = mapped_column(nullable=True) + funding_fee: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) - ft_fee_base: Optional[float] = mapped_column(Float(), nullable=True) + ft_fee_base: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) @property def order_date_utc(self) -> datetime: From 491f49388cc1387e1aa6716f1181c150a396670b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 16 Feb 2023 07:27:57 +0000 Subject: [PATCH 148/186] "Mapped" for trade_model --- freqtrade/persistence/trade_model.py | 95 ++++++++++++++-------------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index c4bfc7b1f..9db79fd8b 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -215,7 +215,7 @@ class Order(ModelBase): # Assumes backtesting will use date_last_filled_utc to calculate future funding fees. self.funding_fee = trade.funding_fees - if (self.ft_order_side == trade.entry_side): + if (self.ft_order_side == trade.entry_side and self.price): trade.open_rate = self.price trade.recalc_trade_from_orders() trade.adjust_stop_loss(trade.open_rate, trade.stop_loss_pct, refresh=True) @@ -1175,78 +1175,77 @@ class Trade(ModelBase, LocalTrade): use_db: bool = True - id: int = mapped_column(Integer, primary_key=True) + id: Mapped[int] = mapped_column(Integer, primary_key=True) - orders: List[Order] = relationship("Order", order_by="Order.id", cascade="all, delete-orphan", - lazy="selectin", innerjoin=True) + orders: Mapped[List[Order]] = relationship( + "Order", order_by="Order.id", cascade="all, delete-orphan", lazy="selectin", + innerjoin=True) - exchange: str = mapped_column(String(25), nullable=False) - pair: str = mapped_column(String(25), nullable=False, index=True) + exchange: Mapped[str] = mapped_column(String(25), nullable=False) + pair: Mapped[str] = mapped_column(String(25), nullable=False, index=True) base_currency = mapped_column(String(25), nullable=True) stake_currency = mapped_column(String(25), nullable=True) - is_open = mapped_column(Boolean, nullable=False, default=True, index=True) - fee_open = mapped_column(Float(), nullable=False, default=0.0) - fee_open_cost = mapped_column(Float(), nullable=True) - fee_open_currency = mapped_column(String(25), nullable=True) - fee_close = mapped_column(Float(), nullable=False, default=0.0) - fee_close_cost = mapped_column(Float(), nullable=True) - fee_close_currency = mapped_column(String(25), nullable=True) - open_rate: float = mapped_column(Float()) + is_open: Mapped[bool] = mapped_column(nullable=False, default=True, index=True) + fee_open: Mapped[float] = mapped_column(Float(), nullable=False, default=0.0) + fee_open_cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + fee_open_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) + fee_close: Mapped[Optional[float]] = mapped_column(Float(), nullable=False, default=0.0) + fee_close_cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + fee_close_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) + open_rate: Mapped[float] = mapped_column(Float()) open_rate_requested: float = mapped_column(Float()) # open_trade_value - calculated via _calc_open_trade_value open_trade_value = mapped_column(Float()) close_rate: Optional[float] = mapped_column(Float()) close_rate_requested: Optional[float] = mapped_column(Float()) - # TODO: is the below type really correct? - realized_profit: float = mapped_column(Float(), default=0.0) - close_profit = mapped_column(Float()) - close_profit_abs: Optional[float] = mapped_column(Float()) - stake_amount: float = mapped_column(Float(), nullable=False) - max_stake_amount: Optional[float] = mapped_column(Float()) - amount: float = mapped_column(Float()) - amount_requested: Optional[float] = mapped_column(Float()) - open_date = mapped_column(DateTime(), nullable=False, default=datetime.utcnow) - close_date = mapped_column(DateTime()) - # TODO: open_order_id type should be Optional[str] - open_order_id: str = mapped_column(String(255)) + realized_profit: Mapped[float] = mapped_column(Float(), default=0.0) + close_profit: Mapped[Optional[float]] = mapped_column(Float()) + close_profit_abs: Mapped[Optional[float]] = mapped_column(Float()) + stake_amount: Mapped[float] = mapped_column(Float(), nullable=False) + max_stake_amount: Mapped[Optional[float]] = mapped_column(Float()) + amount: Mapped[float] = mapped_column(Float()) + amount_requested: Mapped[Optional[float]] = mapped_column(Float()) + open_date: Mapped[datetime] = mapped_column(nullable=False, default=datetime.utcnow) + close_date: Mapped[Optional[datetime]] = mapped_column() + open_order_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) # absolute value of the stop loss - stop_loss = mapped_column(Float(), nullable=True, default=0.0) + stop_loss: Mapped[Optional[float]] = mapped_column(Float(), nullable=True, default=0.0) # percentage value of the stop loss - stop_loss_pct = mapped_column(Float(), nullable=True) + stop_loss_pct: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # absolute value of the initial stop loss - initial_stop_loss = mapped_column(Float(), nullable=True, default=0.0) + initial_stop_loss: Mapped[Optional[float]] = mapped_column(Float(), nullable=True, default=0.0) # percentage value of the initial stop loss - initial_stop_loss_pct = mapped_column(Float(), nullable=True) + initial_stop_loss_pct: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # stoploss order id which is on exchange - stoploss_order_id = mapped_column(String(255), nullable=True, index=True) + stoploss_order_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True, index=True) # last update time of the stoploss order on exchange - stoploss_last_update = mapped_column(DateTime(), nullable=True) + stoploss_last_update: Mapped[Optional[datetime]] = mapped_column(nullable=True) # absolute value of the highest reached price - max_rate = mapped_column(Float(), nullable=True, default=0.0) + max_rate: Mapped[Optional[float]] = mapped_column(Float(), nullable=True, default=0.0) # Lowest price reached - min_rate = mapped_column(Float(), nullable=True) - exit_reason = mapped_column(String(100), nullable=True) - exit_order_status = mapped_column(String(100), nullable=True) - strategy = mapped_column(String(100), nullable=True) - enter_tag = mapped_column(String(100), nullable=True) - timeframe = mapped_column(Integer, nullable=True) + min_rate: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + exit_reason: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) + exit_order_status: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) + strategy: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) + enter_tag: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) + timeframe: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) trading_mode = mapped_column(Enum(TradingMode), nullable=True) - amount_precision = mapped_column(Float(), nullable=True) - price_precision = mapped_column(Float(), nullable=True) - precision_mode = mapped_column(Integer, nullable=True) - contract_size = mapped_column(Float(), nullable=True) + amount_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + price_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + precision_mode: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) + contract_size: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # Leverage trading properties - leverage: float = mapped_column(Float(), nullable=True, default=1.0) - is_short: bool = mapped_column(Boolean, nullable=False, default=False) - liquidation_price = mapped_column(Float(), nullable=True) + leverage: Mapped[Optional[float]] = mapped_column(Float(), nullable=True, default=1.0) + is_short: Mapped[bool] = mapped_column(nullable=False, default=False) + liquidation_price: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # Margin Trading Properties - interest_rate = mapped_column(Float(), nullable=False, default=0.0) + interest_rate: Mapped[Optional[float]] = mapped_column(Float(), nullable=False, default=0.0) # Futures properties - funding_fees: Optional[float] = mapped_column(Float(), nullable=True, default=None) + funding_fees: Mapped[Optional[float]] = mapped_column(Float(), nullable=True, default=None) def __init__(self, **kwargs): super().__init__(**kwargs) From 47b66f322076ac22b986bac0930016adcf3d66bf Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 16 Feb 2023 08:40:34 +0000 Subject: [PATCH 149/186] More fun with types --- freqtrade/persistence/trade_model.py | 10 +++++----- freqtrade/rpc/rpc.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 9db79fd8b..be296c52c 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -1183,8 +1183,8 @@ class Trade(ModelBase, LocalTrade): exchange: Mapped[str] = mapped_column(String(25), nullable=False) pair: Mapped[str] = mapped_column(String(25), nullable=False, index=True) - base_currency = mapped_column(String(25), nullable=True) - stake_currency = mapped_column(String(25), nullable=True) + base_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) + stake_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) is_open: Mapped[bool] = mapped_column(nullable=False, default=True, index=True) fee_open: Mapped[float] = mapped_column(Float(), nullable=False, default=0.0) fee_open_cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) @@ -1209,7 +1209,7 @@ class Trade(ModelBase, LocalTrade): close_date: Mapped[Optional[datetime]] = mapped_column() open_order_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) # absolute value of the stop loss - stop_loss: Mapped[Optional[float]] = mapped_column(Float(), nullable=True, default=0.0) + stop_loss: Mapped[float] = mapped_column(Float(), nullable=True, default=0.0) # percentage value of the stop loss stop_loss_pct: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # absolute value of the initial stop loss @@ -1230,14 +1230,14 @@ class Trade(ModelBase, LocalTrade): enter_tag: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) timeframe: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) - trading_mode = mapped_column(Enum(TradingMode), nullable=True) + trading_mode: Mapped[TradingMode] = mapped_column(Enum(TradingMode), nullable=True) amount_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) price_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) precision_mode: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) contract_size: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # Leverage trading properties - leverage: Mapped[Optional[float]] = mapped_column(Float(), nullable=True, default=1.0) + leverage: Mapped[float] = mapped_column(Float(), nullable=True, default=1.0) is_short: Mapped[bool] = mapped_column(nullable=False, default=False) liquidation_price: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 82f892101..0c1a8fbbd 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -506,7 +506,7 @@ class RPC: trades_df = DataFrame([{'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT), 'profit_abs': trade.close_profit_abs} - for trade in trades if not trade.is_open]) + for trade in trades if not trade.is_open and trade.close_date]) max_drawdown_abs = 0.0 max_drawdown = 0.0 if len(trades_df) > 0: From e59eaf33e09f2b613c44f3ac67a326140c8b6176 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 16 Feb 2023 08:51:32 +0000 Subject: [PATCH 150/186] Update _session to session --- freqtrade/commands/db_commands.py | 2 +- freqtrade/persistence/models.py | 10 ++++++---- tests/persistence/test_migrations.py | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/freqtrade/commands/db_commands.py b/freqtrade/commands/db_commands.py index c424016b1..b4997582d 100644 --- a/freqtrade/commands/db_commands.py +++ b/freqtrade/commands/db_commands.py @@ -20,7 +20,7 @@ def start_convert_db(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) init_db(config['db_url']) - session_target = Trade._session + session_target = Trade.session init_db(config['db_url_from']) logger.info("Starting db migration.") diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 9f90f8a74..f4058b4eb 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -54,10 +54,12 @@ def init_db(db_url: str) -> None: # https://docs.sqlalchemy.org/en/13/orm/contextual.html#thread-local-scope # Scoped sessions proxy requests to the appropriate thread-local session. # We should use the scoped_session object - not a seperately initialized version - Trade._session = scoped_session(sessionmaker(bind=engine, autoflush=False)) - Trade.query = Trade._session.query_property() - Order.query = Trade._session.query_property() - PairLock.query = Trade._session.query_property() + Trade.session = scoped_session(sessionmaker(bind=engine, autoflush=False)) + Order.session = Trade.session + PairLock.session = Trade.session + Trade.query = Trade.session.query_property() + Order.query = Trade.session.query_property() + PairLock.query = Trade.session.query_property() previous_tables = inspect(engine).get_table_names() ModelBase.metadata.create_all(engine) diff --git a/tests/persistence/test_migrations.py b/tests/persistence/test_migrations.py index 2a6959d58..d49b7d207 100644 --- a/tests/persistence/test_migrations.py +++ b/tests/persistence/test_migrations.py @@ -22,7 +22,7 @@ def test_init_create_session(default_conf): # Check if init create a session init_db(default_conf['db_url']) assert hasattr(Trade, '_session') - assert 'scoped_session' in type(Trade._session).__name__ + assert 'scoped_session' in type(Trade.session).__name__ def test_init_custom_db_url(default_conf, tmpdir): @@ -34,7 +34,7 @@ def test_init_custom_db_url(default_conf, tmpdir): init_db(default_conf['db_url']) assert Path(filename).is_file() - r = Trade._session.execute(text("PRAGMA journal_mode")) + r = Trade.session.execute(text("PRAGMA journal_mode")) assert r.first() == ('wal',) From 608a7c2d384e588233d92e37c795e24c7689c0f8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 16 Feb 2023 09:02:57 +0000 Subject: [PATCH 151/186] Add safe_close_rate --- freqtrade/freqtradebot.py | 4 ++-- freqtrade/persistence/trade_model.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index adc630036..0bed27cb9 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1639,7 +1639,7 @@ class FreqtradeBot(LoggingMixin): profit = trade.calc_profit(rate=order_rate, amount=amount, open_rate=trade.open_rate) profit_ratio = trade.calc_profit_ratio(order_rate, amount, trade.open_rate) else: - order_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested + order_rate = trade.safe_close_rate profit = trade.calc_profit(rate=order_rate) + (0.0 if fill else trade.realized_profit) profit_ratio = trade.calc_profit_ratio(order_rate) amount = trade.amount @@ -1694,7 +1694,7 @@ class FreqtradeBot(LoggingMixin): raise DependencyException( f"Order_obj not found for {order_id}. This should not have happened.") - profit_rate: float = trade.close_rate if trade.close_rate else trade.close_rate_requested + profit_rate: float = trade.safe_close_rate profit_trade = trade.calc_profit(rate=profit_rate) current_rate = self.exchange.get_rate( trade.pair, side='exit', is_short=trade.is_short, refresh=False) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index be296c52c..091152196 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -1065,6 +1065,10 @@ class LocalTrade(): """ DEPRECATED! Please use exit_reason instead.""" return self.exit_reason + @property + def safe_close_rate(self) -> float: + return self.close_rate or self.close_rate_requested or 0.0 + @staticmethod def get_trades_proxy(*, pair: Optional[str] = None, is_open: Optional[bool] = None, open_date: Optional[datetime] = None, From 65a5cf64df91f51e5c82bf0fecc8821fa59cd717 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 16 Feb 2023 09:03:32 +0000 Subject: [PATCH 152/186] Re-type session --- freqtrade/persistence/trade_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 091152196..b3ccf7949 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -1175,7 +1175,7 @@ class Trade(ModelBase, LocalTrade): __tablename__ = 'trades' # TODO: Type query type throughout. query: Any - _session: Any = None + session: Any = None use_db: bool = True From 101d9ab87f6208375fc1e990dff38ed275e4cbce Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 19 Feb 2023 19:51:25 +0100 Subject: [PATCH 153/186] Improvements - tests runnable again --- freqtrade/persistence/pairlock.py | 4 ++-- freqtrade/persistence/trade_model.py | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py index 2c81beb85..f8c0586a4 100644 --- a/freqtrade/persistence/pairlock.py +++ b/freqtrade/persistence/pairlock.py @@ -1,5 +1,5 @@ from datetime import datetime, timezone -from typing import Any, Dict, Optional +from typing import Any, ClassVar, Dict, Optional from sqlalchemy import String, or_ from sqlalchemy.orm import Mapped, Query, mapped_column @@ -14,7 +14,7 @@ class PairLock(ModelBase): """ __tablename__ = 'pairlocks' # TODO: Properly type query. - query: Any + query: ClassVar[Any] id: Mapped[int] = mapped_column(primary_key=True) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index b3ccf7949..4b7a09cbe 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -5,10 +5,9 @@ import logging from collections import defaultdict from datetime import datetime, timedelta, timezone from math import isclose -from typing import Any, Dict, List, Optional +from typing import Any, ClassVar, Dict, List, Optional -from sqlalchemy import (Boolean, DateTime, Enum, Float, ForeignKey, Integer, String, - UniqueConstraint, desc, func) +from sqlalchemy import Enum, Float, ForeignKey, Integer, String, UniqueConstraint, desc, func from sqlalchemy.orm import Mapped, Query, lazyload, mapped_column, relationship from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPEN_EXCHANGE_STATES, @@ -37,7 +36,7 @@ class Order(ModelBase): """ __tablename__ = 'orders' # TODO: Properly type query. - query: Any + query: ClassVar[Any] # Uniqueness should be ensured over pair, order_id # its likely that order_id is unique per Pair on some exchanges. __table_args__ = (UniqueConstraint('ft_pair', 'order_id', name="_order_pair_order_id"),) @@ -1174,8 +1173,8 @@ class Trade(ModelBase, LocalTrade): """ __tablename__ = 'trades' # TODO: Type query type throughout. - query: Any - session: Any = None + query: ClassVar[Any] + session: ClassVar[Any] = None use_db: bool = True @@ -1197,11 +1196,11 @@ class Trade(ModelBase, LocalTrade): fee_close_cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) fee_close_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) open_rate: Mapped[float] = mapped_column(Float()) - open_rate_requested: float = mapped_column(Float()) + open_rate_requested: Mapped[float] = mapped_column(Float()) # open_trade_value - calculated via _calc_open_trade_value open_trade_value = mapped_column(Float()) - close_rate: Optional[float] = mapped_column(Float()) - close_rate_requested: Optional[float] = mapped_column(Float()) + close_rate: Mapped[Optional[float]] = mapped_column(Float()) + close_rate_requested: Mapped[Optional[float]] = mapped_column(Float()) realized_profit: Mapped[float] = mapped_column(Float(), default=0.0) close_profit: Mapped[Optional[float]] = mapped_column(Float()) close_profit_abs: Mapped[Optional[float]] = mapped_column(Float()) From 0691bbaad9aeda6fdcc1ec9a9c75edf358974931 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 19 Feb 2023 20:18:46 +0100 Subject: [PATCH 154/186] Update some db types --- freqtrade/persistence/trade_model.py | 2 +- tests/persistence/test_migrations.py | 2 +- tests/persistence/test_persistence.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 4b7a09cbe..abb989940 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -1196,7 +1196,7 @@ class Trade(ModelBase, LocalTrade): fee_close_cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) fee_close_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) open_rate: Mapped[float] = mapped_column(Float()) - open_rate_requested: Mapped[float] = mapped_column(Float()) + open_rate_requested: Mapped[float] = mapped_column(Float(), nullable=True) # open_trade_value - calculated via _calc_open_trade_value open_trade_value = mapped_column(Float()) close_rate: Mapped[Optional[float]] = mapped_column(Float()) diff --git a/tests/persistence/test_migrations.py b/tests/persistence/test_migrations.py index d49b7d207..5254164c1 100644 --- a/tests/persistence/test_migrations.py +++ b/tests/persistence/test_migrations.py @@ -21,7 +21,7 @@ spot, margin, futures = TradingMode.SPOT, TradingMode.MARGIN, TradingMode.FUTURE def test_init_create_session(default_conf): # Check if init create a session init_db(default_conf['db_url']) - assert hasattr(Trade, '_session') + assert hasattr(Trade, 'session') assert 'scoped_session' in type(Trade.session).__name__ diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index d06f05179..6d907ccf0 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -2440,6 +2440,7 @@ def test_select_filled_orders(fee): def test_order_to_ccxt(limit_buy_order_open): order = Order.parse_from_ccxt_object(limit_buy_order_open, 'mocked', 'buy') + order.ft_trade_id = 1 order.query.session.add(order) Order.query.session.commit() From f6b3998bbda8c8ac22e7c13f40b2dfe40217212e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 19 Feb 2023 20:23:04 +0100 Subject: [PATCH 155/186] Fix backtesting type incompatibilities --- freqtrade/freqtradebot.py | 3 ++- freqtrade/optimize/backtesting.py | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 0bed27cb9..ad0628c59 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1330,7 +1330,8 @@ class FreqtradeBot(LoggingMixin): # place new order only if new price is supplied self.execute_entry( pair=trade.pair, - stake_amount=(order_obj.remaining * order_obj.price / trade.leverage), + stake_amount=( + order_obj.safe_remaining * order_obj.safe_price / trade.leverage), price=adjusted_entry_price, trade=trade, is_short=trade.is_short, diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 065a88f40..7f3036037 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -563,7 +563,7 @@ class Backtesting: pos_trade = self._get_exit_for_signal(trade, row, exit_, amount) if pos_trade is not None: order = pos_trade.orders[-1] - if self._get_order_filled(order.price, row): + if self._get_order_filled(order.ft_price, row): order.close_bt_order(current_date, trade) trade.recalc_trade_from_orders() self.wallets.update() @@ -664,6 +664,7 @@ class Backtesting: side=trade.exit_side, order_type=order_type, status="open", + ft_price=close_rate, price=close_rate, average=close_rate, amount=amount, @@ -887,6 +888,7 @@ class Backtesting: order_date=current_time, order_filled_date=current_time, order_update_date=current_time, + ft_price=propose_rate, price=propose_rate, average=propose_rate, amount=amount, @@ -895,7 +897,7 @@ class Backtesting: cost=stake_amount + trade.fee_open, ) trade.orders.append(order) - if pos_adjust and self._get_order_filled(order.price, row): + if pos_adjust and self._get_order_filled(order.ft_price, row): order.close_bt_order(current_time, trade) else: trade.open_order_id = str(self.order_id_counter) @@ -1008,15 +1010,15 @@ class Backtesting: # only check on new candles for open entry orders if order.side == trade.entry_side and current_time > order.order_date_utc: requested_rate = strategy_safe_wrapper(self.strategy.adjust_entry_price, - default_retval=order.price)( + default_retval=order.ft_price)( trade=trade, # type: ignore[arg-type] order=order, pair=trade.pair, current_time=current_time, - proposed_rate=row[OPEN_IDX], current_order_rate=order.price, + proposed_rate=row[OPEN_IDX], current_order_rate=order.ft_price, entry_tag=trade.enter_tag, side=trade.trade_direction ) # default value is current order price # cancel existing order whenever a new rate is requested (or None) - if requested_rate == order.price: + if requested_rate == order.ft_price: # assumption: there can't be multiple open entry orders at any given time return False else: @@ -1028,7 +1030,8 @@ class Backtesting: if requested_rate: self._enter_trade(pair=trade.pair, row=row, trade=trade, requested_rate=requested_rate, - requested_stake=(order.remaining * order.price / trade.leverage), + requested_stake=( + order.safe_remaining * order.ft_price / trade.leverage), direction='short' if trade.is_short else 'long') self.replaced_entry_orders += 1 else: @@ -1095,7 +1098,7 @@ class Backtesting: for trade in list(LocalTrade.bt_trades_open_pp[pair]): # 3. Process entry orders. order = trade.select_order(trade.entry_side, is_open=True) - if order and self._get_order_filled(order.price, row): + if order and self._get_order_filled(order.ft_price, row): order.close_bt_order(current_time, trade) trade.open_order_id = None self.wallets.update() @@ -1106,7 +1109,7 @@ class Backtesting: # 5. Process exit orders. order = trade.select_order(trade.exit_side, is_open=True) - if order and self._get_order_filled(order.price, row): + if order and self._get_order_filled(order.ft_price, row): order.close_bt_order(current_time, trade) trade.open_order_id = None sub_trade = order.safe_amount_after_fee != trade.amount @@ -1115,7 +1118,7 @@ class Backtesting: trade.recalc_trade_from_orders() else: trade.close_date = current_time - trade.close(order.price, show_msg=False) + trade.close(order.ft_price, show_msg=False) # logger.debug(f"{pair} - Backtesting exit {trade}") LocalTrade.close_bt_trade(trade) From 8765e3a4d63a30e1711d6701553966d74d867694 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 Feb 2023 07:12:09 +0100 Subject: [PATCH 156/186] Fix some Type issues --- freqtrade/rpc/rpc.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 0c1a8fbbd..f8537b0f8 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -189,8 +189,8 @@ class RPC: else: # Closed trade ... current_rate = trade.close_rate - current_profit = trade.close_profit - current_profit_abs = trade.close_profit_abs + current_profit = trade.close_profit or 0.0 + current_profit_abs = trade.close_profit_abs or 0.0 total_profit_abs = trade.realized_profit + current_profit_abs # Calculate fiat profit @@ -449,11 +449,11 @@ class RPC: durations.append((trade.close_date - trade.open_date).total_seconds()) if not trade.is_open: - profit_ratio = trade.close_profit - profit_abs = trade.close_profit_abs + profit_ratio = trade.close_profit or 0.0 + profit_abs = trade.close_profit_abs or 0.0 profit_closed_coin.append(profit_abs) profit_closed_ratio.append(profit_ratio) - if trade.close_profit >= 0: + if profit_ratio >= 0: winning_trades += 1 winning_profit += profit_abs else: From c2c039151cbcdc176ecc506bf6ee6b8fc2d311f4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 Feb 2023 18:26:32 +0100 Subject: [PATCH 157/186] Improve typesafety around trade object --- freqtrade/freqtradebot.py | 9 +++++++-- freqtrade/rpc/rpc.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ad0628c59..cec7176f6 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1314,7 +1314,7 @@ class FreqtradeBot(LoggingMixin): default_retval=order_obj.price)( trade=trade, order=order_obj, pair=trade.pair, current_time=datetime.now(timezone.utc), proposed_rate=proposed_rate, - current_order_rate=order_obj.price, entry_tag=trade.enter_tag, + current_order_rate=order_obj.safe_price, entry_tag=trade.enter_tag, side=trade.entry_side) replacing = True @@ -1345,6 +1345,8 @@ class FreqtradeBot(LoggingMixin): """ for trade in Trade.get_open_order_trades(): + if not trade.open_order_id: + continue try: order = self.exchange.fetch_order(trade.open_order_id, trade.pair) except (ExchangeError): @@ -1369,6 +1371,9 @@ class FreqtradeBot(LoggingMixin): """ was_trade_fully_canceled = False side = trade.entry_side.capitalize() + if not trade.open_order_id: + logger.warning(f"No open order for {trade}.") + return False # Cancelled orders may have the status of 'canceled' or 'closed' if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES: @@ -1455,7 +1460,7 @@ class FreqtradeBot(LoggingMixin): return False try: - co = self.exchange.cancel_order_with_result(trade.open_order_id, trade.pair, + co = self.exchange.cancel_order_with_result(order['id'], trade.pair, trade.amount) except InvalidOrderException: logger.exception( diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index f8537b0f8..38f478f4c 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -410,7 +410,7 @@ class RPC: exit_reasons[trade.exit_reason][trade_win_loss(trade)] += 1 # Duration - dur: Dict[str, List[int]] = {'wins': [], 'draws': [], 'losses': []} + dur: Dict[str, List[float]] = {'wins': [], 'draws': [], 'losses': []} for trade in trades: if trade.close_date is not None and trade.open_date is not None: trade_dur = (trade.close_date - trade.open_date).total_seconds() From db4f4498dc6583e6a1eb9e3183f6b11c6232de96 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 Feb 2023 20:00:01 +0100 Subject: [PATCH 158/186] Experimentally type query property ... --- freqtrade/persistence/models.py | 3 ++- freqtrade/persistence/pairlock.py | 6 ++++-- freqtrade/persistence/trade_model.py | 12 +++++++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index f4058b4eb..b94be950a 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -6,7 +6,7 @@ from typing import Any, Dict from sqlalchemy import create_engine, inspect from sqlalchemy.exc import NoSuchModuleError -from sqlalchemy.orm import scoped_session, sessionmaker +from sqlalchemy.orm import Session, scoped_session, sessionmaker from sqlalchemy.pool import StaticPool from freqtrade.exceptions import OperationalException @@ -20,6 +20,7 @@ logger = logging.getLogger(__name__) _SQL_DOCS_URL = 'http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls' +SessionType = scoped_session[Session] def init_db(db_url: str) -> None: diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py index f8c0586a4..b10d74693 100644 --- a/freqtrade/persistence/pairlock.py +++ b/freqtrade/persistence/pairlock.py @@ -3,9 +3,11 @@ from typing import Any, ClassVar, Dict, Optional from sqlalchemy import String, or_ from sqlalchemy.orm import Mapped, Query, mapped_column +from sqlalchemy.orm.scoping import _QueryDescriptorType from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.persistence.base import ModelBase +from freqtrade.persistence.models import SessionType class PairLock(ModelBase): @@ -13,8 +15,8 @@ class PairLock(ModelBase): Pair Locks database model. """ __tablename__ = 'pairlocks' - # TODO: Properly type query. - query: ClassVar[Any] + query: ClassVar[_QueryDescriptorType] + session: ClassVar[SessionType] id: Mapped[int] = mapped_column(primary_key=True) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index abb989940..10643db17 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -9,6 +9,7 @@ from typing import Any, ClassVar, Dict, List, Optional from sqlalchemy import Enum, Float, ForeignKey, Integer, String, UniqueConstraint, desc, func from sqlalchemy.orm import Mapped, Query, lazyload, mapped_column, relationship +from sqlalchemy.orm.scoping import _QueryDescriptorType from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPEN_EXCHANGE_STATES, BuySell, LongShort) @@ -17,6 +18,7 @@ from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange import amount_to_contract_precision, price_to_precision from freqtrade.leverage import interest from freqtrade.persistence.base import ModelBase +from freqtrade.persistence.models import SessionType from freqtrade.util import FtPrecise @@ -35,8 +37,9 @@ class Order(ModelBase): Mirrors CCXT Order structure """ __tablename__ = 'orders' - # TODO: Properly type query. - query: ClassVar[Any] + query: ClassVar[_QueryDescriptorType] + session: ClassVar[SessionType] + # Uniqueness should be ensured over pair, order_id # its likely that order_id is unique per Pair on some exchanges. __table_args__ = (UniqueConstraint('ft_pair', 'order_id', name="_order_pair_order_id"),) @@ -1172,9 +1175,8 @@ class Trade(ModelBase, LocalTrade): Note: Fields must be aligned with LocalTrade class """ __tablename__ = 'trades' - # TODO: Type query type throughout. - query: ClassVar[Any] - session: ClassVar[Any] = None + query: ClassVar[_QueryDescriptorType] + session: ClassVar[SessionType] = None use_db: bool = True From b65cff0adcd6498a0dfe288a97c93488c3bb7fab Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 Feb 2023 20:22:41 +0100 Subject: [PATCH 159/186] Update "Query" type --- freqtrade/persistence/trade_model.py | 2 +- freqtrade/rpc/rpc.py | 9 +++++---- freqtrade/rpc/telegram.py | 10 +++++++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 10643db17..0f85528e0 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -1305,7 +1305,7 @@ class Trade(ModelBase, LocalTrade): ) @staticmethod - def get_trades(trade_filter=None, include_orders: bool = True) -> Query: + def get_trades(trade_filter=None, include_orders: bool = True) -> Query['Trade']: """ Helper function to query Trades using filters. NOTE: Not supported in Backtesting. diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 38f478f4c..8692c477f 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -373,13 +373,13 @@ class RPC: def _rpc_trade_history(self, limit: int, offset: int = 0, order_by_id: bool = False) -> Dict: """ Returns the X last trades """ - order_by = Trade.id if order_by_id else Trade.close_date.desc() + order_by: Any = Trade.id if order_by_id else Trade.close_date.desc() if limit: trades = Trade.get_trades([Trade.is_open.is_(False)]).order_by( order_by).limit(limit).offset(offset) else: trades = Trade.get_trades([Trade.is_open.is_(False)]).order_by( - Trade.close_date.desc()).all() + Trade.close_date.desc()) output = [trade.to_json() for trade in trades] @@ -401,7 +401,7 @@ class RPC: return 'losses' else: return 'draws' - trades: List[Trade] = Trade.get_trades([Trade.is_open.is_(False)], include_orders=False) + trades = Trade.get_trades([Trade.is_open.is_(False)], include_orders=False) # Sell reason exit_reasons = {} for trade in trades: @@ -785,7 +785,8 @@ class RPC: # check if valid pair # check if pair already has an open pair - trade: Trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() + trade: Optional[Trade] = Trade.get_trades( + [Trade.is_open.is_(True), Trade.pair == pair]).first() is_short = (order_side == SignalDirection.SHORT) if trade: is_short = trade.is_short diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 7bbeea2a2..dc92478ab 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1055,10 +1055,14 @@ class Telegram(RPCHandler): query.answer() query.edit_message_text(text="Force exit canceled.") return - trade: Trade = Trade.get_trades(trade_filter=Trade.id == trade_id).first() + trade: Optional[Trade] = Trade.get_trades(trade_filter=Trade.id == trade_id).first() query.answer() - query.edit_message_text(text=f"Manually exiting Trade #{trade_id}, {trade.pair}") - self._force_exit_action(trade_id) + if trade: + query.edit_message_text( + text=f"Manually exiting Trade #{trade_id}, {trade.pair}") + self._force_exit_action(trade_id) + else: + query.edit_message_text(text=f"Trade {trade_id} not found.") def _force_enter_action(self, pair, price: Optional[float], order_side: SignalDirection): if pair != 'cancel': From 764001a4c290ba9bca30a224171f4f88ef20001f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Feb 2023 06:49:15 +0100 Subject: [PATCH 160/186] Don't reuse variable --- freqtrade/data/btanalysis.py | 2 +- freqtrade/persistence/pairlock_middleware.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index c682436c7..9772506a7 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -346,7 +346,7 @@ def evaluate_result_multi(results: pd.DataFrame, timeframe: str, return df_final[df_final['open_trades'] > max_open_trades] -def trade_list_to_dataframe(trades: List[LocalTrade]) -> pd.DataFrame: +def trade_list_to_dataframe(trades: Union[List[Trade], List[LocalTrade]]) -> pd.DataFrame: """ Convert list of Trade objects to pandas Dataframe :param trades: List of trade objects diff --git a/freqtrade/persistence/pairlock_middleware.py b/freqtrade/persistence/pairlock_middleware.py index 4485bb88e..5ed131a9b 100644 --- a/freqtrade/persistence/pairlock_middleware.py +++ b/freqtrade/persistence/pairlock_middleware.py @@ -133,8 +133,8 @@ class PairLocks(): PairLock.query.session.commit() else: # used in backtesting mode; don't show log messages for speed - locks = PairLocks.get_pair_locks(None) - for lock in locks: + locksb = PairLocks.get_pair_locks(None) + for lock in locksb: if lock.reason == reason: lock.active = False From f2f4158974b294339ba3c22352b44c4dd9f31c5d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Feb 2023 06:58:21 +0100 Subject: [PATCH 161/186] Bump sqlalchemy to 2.0.4 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3e6c66757..6b1c888b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pandas-ta==0.3.14b ccxt==2.8.54 cryptography==39.0.1 aiohttp==3.8.4 -SQLAlchemy==2.0.3 +SQLAlchemy==2.0.4 python-telegram-bot==13.15 arrow==1.2.3 cachetools==4.2.2 From d175ab495b39a9a249274b97ea52292840592283 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 07:03:22 +0100 Subject: [PATCH 162/186] Move SessionType to base module --- freqtrade/persistence/base.py | 4 +++- freqtrade/persistence/models.py | 5 ++--- freqtrade/persistence/pairlock.py | 3 +-- freqtrade/persistence/trade_model.py | 3 +-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/freqtrade/persistence/base.py b/freqtrade/persistence/base.py index 2fed715d7..98e483c90 100644 --- a/freqtrade/persistence/base.py +++ b/freqtrade/persistence/base.py @@ -1,6 +1,8 @@ -from sqlalchemy.orm import DeclarativeBase +from sqlalchemy.orm import DeclarativeBase, Session, scoped_session +SessionType = scoped_session[Session] + class ModelBase(DeclarativeBase): pass diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index b94be950a..98d1d7a8a 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -6,11 +6,11 @@ from typing import Any, Dict from sqlalchemy import create_engine, inspect from sqlalchemy.exc import NoSuchModuleError -from sqlalchemy.orm import Session, scoped_session, sessionmaker +from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.pool import StaticPool from freqtrade.exceptions import OperationalException -from freqtrade.persistence.base import ModelBase +from freqtrade.persistence.base import ModelBase, SessionType from freqtrade.persistence.migrations import check_migrate from freqtrade.persistence.pairlock import PairLock from freqtrade.persistence.trade_model import Order, Trade @@ -20,7 +20,6 @@ logger = logging.getLogger(__name__) _SQL_DOCS_URL = 'http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls' -SessionType = scoped_session[Session] def init_db(db_url: str) -> None: diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py index b10d74693..d5a8d7ae1 100644 --- a/freqtrade/persistence/pairlock.py +++ b/freqtrade/persistence/pairlock.py @@ -6,8 +6,7 @@ from sqlalchemy.orm import Mapped, Query, mapped_column from sqlalchemy.orm.scoping import _QueryDescriptorType from freqtrade.constants import DATETIME_PRINT_FORMAT -from freqtrade.persistence.base import ModelBase -from freqtrade.persistence.models import SessionType +from freqtrade.persistence.base import ModelBase, SessionType class PairLock(ModelBase): diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 0f85528e0..ce7d56745 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -17,8 +17,7 @@ from freqtrade.enums import ExitType, TradingMode from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange import amount_to_contract_precision, price_to_precision from freqtrade.leverage import interest -from freqtrade.persistence.base import ModelBase -from freqtrade.persistence.models import SessionType +from freqtrade.persistence.base import ModelBase, SessionType from freqtrade.util import FtPrecise From 0f914cf2bd54902ceb96c29499eca888b4422b03 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 07:24:20 +0100 Subject: [PATCH 163/186] Use Mapped for LocalTrade this won't initialize sqlalchemy, as the base class is not inheriting from sqlalchemy. --- freqtrade/persistence/trade_model.py | 108 +++++++++++++-------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index ce7d56745..93701b1ae 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -288,76 +288,76 @@ class LocalTrade(): bt_trades_open_pp: Dict[str, List['LocalTrade']] = defaultdict(list) bt_open_open_trade_count: int = 0 total_profit: float = 0 - realized_profit: float = 0 + realized_profit: Mapped[float] = 0 # type: ignore - id: int = 0 + id: Mapped[int] = 0 # type: ignore - orders: List[Order] = [] + orders: Mapped[List[Order]] = [] # type: ignore - exchange: str = '' - pair: str = '' - base_currency: str = '' - stake_currency: str = '' - is_open: bool = True - fee_open: float = 0.0 - fee_open_cost: Optional[float] = None - fee_open_currency: str = '' - fee_close: float = 0.0 - fee_close_cost: Optional[float] = None - fee_close_currency: str = '' - open_rate: float = 0.0 - open_rate_requested: Optional[float] = None + exchange: Mapped[str] = '' # type: ignore + pair: Mapped[str] = '' # type: ignore + base_currency: Mapped[Optional[str]] = '' # type: ignore + stake_currency: Mapped[Optional[str]] = '' # type: ignore + is_open: Mapped[bool] = True # type: ignore + fee_open: Mapped[float] = 0.0 # type: ignore + fee_open_cost: Mapped[Optional[float]] = None # type: ignore + fee_open_currency: Mapped[Optional[str]] = '' # type: ignore + fee_close: Mapped[Optional[float]] = 0.0 # type: ignore + fee_close_cost: Mapped[Optional[float]] = None # type: ignore + fee_close_currency: Mapped[Optional[str]] = '' # type: ignore + open_rate: Mapped[float] = 0.0 # type: ignore + open_rate_requested: Mapped[Optional[float]] = None # type: ignore # open_trade_value - calculated via _calc_open_trade_value - open_trade_value: float = 0.0 - close_rate: Optional[float] = None - close_rate_requested: Optional[float] = None - close_profit: Optional[float] = None - close_profit_abs: Optional[float] = None - stake_amount: float = 0.0 - max_stake_amount: float = 0.0 - amount: float = 0.0 - amount_requested: Optional[float] = None - open_date: datetime - close_date: Optional[datetime] = None - open_order_id: Optional[str] = None + open_trade_value: Mapped[float] = 0.0 # type: ignore + close_rate: Mapped[Optional[float]] = None # type: ignore + close_rate_requested: Mapped[Optional[float]] = None # type: ignore + close_profit: Mapped[Optional[float]] = None # type: ignore + close_profit_abs: Mapped[Optional[float]] = None # type: ignore + stake_amount: Mapped[float] = 0.0 # type: ignore + max_stake_amount: Mapped[Optional[float]] = 0.0 # type: ignore + amount: Mapped[float] = 0.0 # type: ignore + amount_requested: Mapped[Optional[float]] = None # type: ignore + open_date: Mapped[datetime] + close_date: Mapped[Optional[datetime]] = None # type: ignore + open_order_id: Mapped[Optional[str]] = None # type: ignore # absolute value of the stop loss - stop_loss: float = 0.0 + stop_loss: Mapped[float] = 0.0 # type: ignore # percentage value of the stop loss - stop_loss_pct: float = 0.0 + stop_loss_pct: Mapped[Optional[float]] = 0.0 # type: ignore # absolute value of the initial stop loss - initial_stop_loss: float = 0.0 + initial_stop_loss: Mapped[Optional[float]] = 0.0 # type: ignore # percentage value of the initial stop loss - initial_stop_loss_pct: Optional[float] = None + initial_stop_loss_pct: Mapped[Optional[float]] = None # type: ignore # stoploss order id which is on exchange - stoploss_order_id: Optional[str] = None + stoploss_order_id: Mapped[Optional[str]] = None # type: ignore # last update time of the stoploss order on exchange - stoploss_last_update: Optional[datetime] = None + stoploss_last_update: Mapped[Optional[datetime]] = None # type: ignore # absolute value of the highest reached price - max_rate: float = 0.0 + max_rate: Mapped[Optional[float]] = None # type: ignore # Lowest price reached - min_rate: float = 0.0 - exit_reason: str = '' - exit_order_status: str = '' - strategy: str = '' - enter_tag: Optional[str] = None - timeframe: Optional[int] = None + min_rate: Mapped[Optional[float]] = None # type: ignore + exit_reason: Mapped[Optional[str]] = '' # type: ignore + exit_order_status: Mapped[Optional[str]] = '' # type: ignore + strategy: Mapped[Optional[str]] = '' # type: ignore + enter_tag: Mapped[Optional[str]] = None # type: ignore + timeframe: Mapped[Optional[int]] = None # type: ignore - trading_mode: TradingMode = TradingMode.SPOT - amount_precision: Optional[float] = None - price_precision: Optional[float] = None - precision_mode: Optional[int] = None - contract_size: Optional[float] = None + trading_mode: Mapped[TradingMode] = TradingMode.SPOT # type: ignore + amount_precision: Mapped[Optional[float]] = None # type: ignore + price_precision: Mapped[Optional[float]] = None # type: ignore + precision_mode: Mapped[Optional[int]] = None # type: ignore + contract_size: Mapped[Optional[float]] = None # type: ignore # Leverage trading properties - liquidation_price: Optional[float] = None - is_short: bool = False - leverage: float = 1.0 + liquidation_price: Mapped[Optional[float]] = None # type: ignore + is_short: Mapped[bool] = False # type: ignore + leverage: Mapped[float] = 1.0 # type: ignore # Margin trading properties - interest_rate: float = 0.0 + interest_rate: Mapped[float] = 0.0 # type: ignore # Futures properties - funding_fees: Optional[float] = None + funding_fees: Mapped[Optional[float]] = None # type: ignore @property def stoploss_or_liquidation(self) -> float: @@ -1175,7 +1175,7 @@ class Trade(ModelBase, LocalTrade): """ __tablename__ = 'trades' query: ClassVar[_QueryDescriptorType] - session: ClassVar[SessionType] = None + session: ClassVar[SessionType] use_db: bool = True @@ -1197,7 +1197,7 @@ class Trade(ModelBase, LocalTrade): fee_close_cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) fee_close_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) open_rate: Mapped[float] = mapped_column(Float()) - open_rate_requested: Mapped[float] = mapped_column(Float(), nullable=True) + open_rate_requested: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # open_trade_value - calculated via _calc_open_trade_value open_trade_value = mapped_column(Float()) close_rate: Mapped[Optional[float]] = mapped_column(Float()) @@ -1246,7 +1246,7 @@ class Trade(ModelBase, LocalTrade): liquidation_price: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # Margin Trading Properties - interest_rate: Mapped[Optional[float]] = mapped_column(Float(), nullable=False, default=0.0) + interest_rate: Mapped[float] = mapped_column(Float(), nullable=False, default=0.0) # Futures properties funding_fees: Mapped[Optional[float]] = mapped_column(Float(), nullable=True, default=None) From 7c09c0178815a725a3ac0df5509e7a0d5100a475 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Feb 2023 07:27:01 +0100 Subject: [PATCH 164/186] Add some more typehints --- freqtrade/persistence/trade_model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 93701b1ae..887784be3 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -154,7 +154,7 @@ class Order(ModelBase): self.order_update_date = datetime.now(timezone.utc) def to_ccxt_object(self) -> Dict[str, Any]: - order = { + order: Dict[str, Any] = { 'id': self.order_id, 'symbol': self.ft_pair, 'price': self.price, @@ -1062,7 +1062,7 @@ class LocalTrade(): return len(self.select_filled_orders('sell')) @property - def sell_reason(self) -> str: + def sell_reason(self) -> Optional[str]: """ DEPRECATED! Please use exit_reason instead.""" return self.exit_reason @@ -1276,7 +1276,7 @@ class Trade(ModelBase, LocalTrade): def get_trades_proxy(*, pair: Optional[str] = None, is_open: Optional[bool] = None, open_date: Optional[datetime] = None, close_date: Optional[datetime] = None, - ) -> List['LocalTrade']: + ) -> List['LocalTrade', 'Trade']: """ Helper function to query Trades.j Returns a List of trades, filtered on the parameters given. From b5f55c9b145129567d5d642efb7229fbf36c4ff9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Mar 2023 06:40:07 +0100 Subject: [PATCH 165/186] Improve type safety in backtesting --- freqtrade/optimize/backtesting.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 7f3036037..1f868f7bf 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -440,7 +440,8 @@ class Backtesting: side_1 * abs(self.strategy.trailing_stop_positive / leverage))) else: # Worst case: price ticks tiny bit above open and dives down. - stop_rate = row[OPEN_IDX] * (1 - side_1 * abs(trade.stop_loss_pct / leverage)) + stop_rate = row[OPEN_IDX] * (1 - side_1 * abs( + (trade.stop_loss_pct or 0.0) / leverage)) if is_short: assert stop_rate > row[LOW_IDX] else: @@ -472,7 +473,7 @@ class Backtesting: # - (Expected abs profit - open_rate - open_fee) / (fee_close -1) roi_rate = trade.open_rate * roi / leverage open_fee_rate = side_1 * trade.open_rate * (1 + side_1 * trade.fee_open) - close_rate = -(roi_rate + open_fee_rate) / (trade.fee_close - side_1 * 1) + close_rate = -(roi_rate + open_fee_rate) / ((trade.fee_close or 0.0) - side_1 * 1) if is_short: is_new_roi = row[OPEN_IDX] < close_rate else: From e5c9cde36f713c50a7085c7ad4c9377a97bd34b8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Mar 2023 07:23:26 +0100 Subject: [PATCH 166/186] Update trades_proxy typing --- freqtrade/persistence/trade_model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 887784be3..531e86282 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -5,7 +5,7 @@ import logging from collections import defaultdict from datetime import datetime, timedelta, timezone from math import isclose -from typing import Any, ClassVar, Dict, List, Optional +from typing import Any, ClassVar, Dict, List, Optional, cast from sqlalchemy import Enum, Float, ForeignKey, Integer, String, UniqueConstraint, desc, func from sqlalchemy.orm import Mapped, Query, lazyload, mapped_column, relationship @@ -1131,7 +1131,7 @@ class LocalTrade(): @staticmethod def get_open_trades() -> List[Any]: """ - Query trades from persistence layer + Retrieve open trades """ return Trade.get_trades_proxy(is_open=True) @@ -1276,7 +1276,7 @@ class Trade(ModelBase, LocalTrade): def get_trades_proxy(*, pair: Optional[str] = None, is_open: Optional[bool] = None, open_date: Optional[datetime] = None, close_date: Optional[datetime] = None, - ) -> List['LocalTrade', 'Trade']: + ) -> List['LocalTrade']: """ Helper function to query Trades.j Returns a List of trades, filtered on the parameters given. @@ -1295,7 +1295,7 @@ class Trade(ModelBase, LocalTrade): trade_filter.append(Trade.close_date > close_date) if is_open is not None: trade_filter.append(Trade.is_open.is_(is_open)) - return Trade.get_trades(trade_filter).all() + return cast(List[LocalTrade], Trade.get_trades(trade_filter).all()) else: return LocalTrade.get_trades_proxy( pair=pair, is_open=is_open, From a1166b1077ca96db704d97e5d3775d960ee67ba9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Mar 2023 07:25:33 +0100 Subject: [PATCH 167/186] allow null fee on calc_base_close --- freqtrade/persistence/trade_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 531e86282..7eabfab0f 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -795,10 +795,10 @@ class LocalTrade(): return interest(exchange_name=self.exchange, borrowed=borrowed, rate=rate, hours=hours) - def _calc_base_close(self, amount: FtPrecise, rate: float, fee: float) -> FtPrecise: + def _calc_base_close(self, amount: FtPrecise, rate: float, fee: Optional[float]) -> FtPrecise: close_trade = amount * FtPrecise(rate) - fees = close_trade * FtPrecise(fee) + fees = close_trade * FtPrecise(fee or 0.0) if self.is_short: return close_trade + fees From 4a35d32b6ae84b2c0e372a7e00df283b7df76078 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Mar 2023 07:26:35 +0100 Subject: [PATCH 168/186] Improve trade stop types --- freqtrade/persistence/trade_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 7eabfab0f..d747971fb 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -595,7 +595,7 @@ class LocalTrade(): self.stop_loss_pct = -1 * abs(percent) - def adjust_stop_loss(self, current_price: float, stoploss: float, + def adjust_stop_loss(self, current_price: float, stoploss: Optional[float], initial: bool = False, refresh: bool = False) -> None: """ This adjusts the stop loss to it's most recently observed setting @@ -604,7 +604,7 @@ class LocalTrade(): :param initial: Called to initiate stop_loss. Skips everything if self.stop_loss is already set. """ - if initial and not (self.stop_loss is None or self.stop_loss == 0): + if stoploss is None or (initial and not (self.stop_loss is None or self.stop_loss == 0)): # Don't modify if called with initial and nothing to do return refresh = True if refresh and self.nr_of_successful_entries == 1 else False From 874413ccc59ebe0ab7346de74ea3ff06c82b513c Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Mar 2023 11:16:56 +0000 Subject: [PATCH 169/186] Fix some style violations --- freqtrade/persistence/base.py | 1 + freqtrade/persistence/models.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/persistence/base.py b/freqtrade/persistence/base.py index 98e483c90..fc2dac75e 100644 --- a/freqtrade/persistence/base.py +++ b/freqtrade/persistence/base.py @@ -4,5 +4,6 @@ from sqlalchemy.orm import DeclarativeBase, Session, scoped_session SessionType = scoped_session[Session] + class ModelBase(DeclarativeBase): pass diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 98d1d7a8a..f4058b4eb 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -10,7 +10,7 @@ from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.pool import StaticPool from freqtrade.exceptions import OperationalException -from freqtrade.persistence.base import ModelBase, SessionType +from freqtrade.persistence.base import ModelBase from freqtrade.persistence.migrations import check_migrate from freqtrade.persistence.pairlock import PairLock from freqtrade.persistence.trade_model import Order, Trade From 388dfec50bbf3ff4884dae25b4edf213a203edf8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Mar 2023 18:17:37 +0100 Subject: [PATCH 170/186] Remove last type error --- freqtrade/persistence/trade_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index d747971fb..ab7d56766 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -643,7 +643,7 @@ class LocalTrade(): f"initial_stop_loss={self.initial_stop_loss:.8f}, " f"stop_loss={self.stop_loss:.8f}. " f"Trailing stoploss saved us: " - f"{float(self.stop_loss) - float(self.initial_stop_loss):.8f}.") + f"{float(self.stop_loss) - float(self.initial_stop_loss or 0.0):.8f}.") def update_trade(self, order: Order) -> None: """ From f0f72fdd33db951f6bbc24a8e93a6f0acdadafb3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Mar 2023 19:48:15 +0100 Subject: [PATCH 171/186] Don't define "mapped" on LocalTrade class --- freqtrade/persistence/trade_model.py | 220 ++++++++++++++------------- 1 file changed, 118 insertions(+), 102 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index ab7d56766..62e2e2a7f 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -288,76 +288,76 @@ class LocalTrade(): bt_trades_open_pp: Dict[str, List['LocalTrade']] = defaultdict(list) bt_open_open_trade_count: int = 0 total_profit: float = 0 - realized_profit: Mapped[float] = 0 # type: ignore + realized_profit: float = 0 - id: Mapped[int] = 0 # type: ignore + id: int = 0 - orders: Mapped[List[Order]] = [] # type: ignore + orders: List[Order] = [] - exchange: Mapped[str] = '' # type: ignore - pair: Mapped[str] = '' # type: ignore - base_currency: Mapped[Optional[str]] = '' # type: ignore - stake_currency: Mapped[Optional[str]] = '' # type: ignore - is_open: Mapped[bool] = True # type: ignore - fee_open: Mapped[float] = 0.0 # type: ignore - fee_open_cost: Mapped[Optional[float]] = None # type: ignore - fee_open_currency: Mapped[Optional[str]] = '' # type: ignore - fee_close: Mapped[Optional[float]] = 0.0 # type: ignore - fee_close_cost: Mapped[Optional[float]] = None # type: ignore - fee_close_currency: Mapped[Optional[str]] = '' # type: ignore - open_rate: Mapped[float] = 0.0 # type: ignore - open_rate_requested: Mapped[Optional[float]] = None # type: ignore + exchange: str = '' + pair: str = '' + base_currency: Optional[str] = '' + stake_currency: Optional[str] = '' + is_open: bool = True + fee_open: float = 0.0 + fee_open_cost: Optional[float] = None + fee_open_currency: Optional[str] = '' + fee_close: Optional[float] = 0.0 + fee_close_cost: Optional[float] = None + fee_close_currency: Optional[str] = '' + open_rate: float = 0.0 + open_rate_requested: Optional[float] = None # open_trade_value - calculated via _calc_open_trade_value - open_trade_value: Mapped[float] = 0.0 # type: ignore - close_rate: Mapped[Optional[float]] = None # type: ignore - close_rate_requested: Mapped[Optional[float]] = None # type: ignore - close_profit: Mapped[Optional[float]] = None # type: ignore - close_profit_abs: Mapped[Optional[float]] = None # type: ignore - stake_amount: Mapped[float] = 0.0 # type: ignore - max_stake_amount: Mapped[Optional[float]] = 0.0 # type: ignore - amount: Mapped[float] = 0.0 # type: ignore - amount_requested: Mapped[Optional[float]] = None # type: ignore - open_date: Mapped[datetime] - close_date: Mapped[Optional[datetime]] = None # type: ignore - open_order_id: Mapped[Optional[str]] = None # type: ignore + open_trade_value: float = 0.0 + close_rate: Optional[float] = None + close_rate_requested: Optional[float] = None + close_profit: Optional[float] = None + close_profit_abs: Optional[float] = None + stake_amount: float = 0.0 + max_stake_amount: Optional[float] = 0.0 + amount: float = 0.0 + amount_requested: Optional[float] = None + open_date: datetime + close_date: Optional[datetime] = None + open_order_id: Optional[str] = None # absolute value of the stop loss - stop_loss: Mapped[float] = 0.0 # type: ignore + stop_loss: float = 0.0 # percentage value of the stop loss - stop_loss_pct: Mapped[Optional[float]] = 0.0 # type: ignore + stop_loss_pct: Optional[float] = 0.0 # absolute value of the initial stop loss - initial_stop_loss: Mapped[Optional[float]] = 0.0 # type: ignore + initial_stop_loss: Optional[float] = 0.0 # percentage value of the initial stop loss - initial_stop_loss_pct: Mapped[Optional[float]] = None # type: ignore + initial_stop_loss_pct: Optional[float] = None # stoploss order id which is on exchange - stoploss_order_id: Mapped[Optional[str]] = None # type: ignore + stoploss_order_id: Optional[str] = None # last update time of the stoploss order on exchange - stoploss_last_update: Mapped[Optional[datetime]] = None # type: ignore + stoploss_last_update: Optional[datetime] = None # absolute value of the highest reached price - max_rate: Mapped[Optional[float]] = None # type: ignore + max_rate: Optional[float] = None # Lowest price reached - min_rate: Mapped[Optional[float]] = None # type: ignore - exit_reason: Mapped[Optional[str]] = '' # type: ignore - exit_order_status: Mapped[Optional[str]] = '' # type: ignore - strategy: Mapped[Optional[str]] = '' # type: ignore - enter_tag: Mapped[Optional[str]] = None # type: ignore - timeframe: Mapped[Optional[int]] = None # type: ignore + min_rate: Optional[float] = None + exit_reason: Optional[str] = '' + exit_order_status: Optional[str] = '' + strategy: Optional[str] = '' + enter_tag: Optional[str] = None + timeframe: Optional[int] = None - trading_mode: Mapped[TradingMode] = TradingMode.SPOT # type: ignore - amount_precision: Mapped[Optional[float]] = None # type: ignore - price_precision: Mapped[Optional[float]] = None # type: ignore - precision_mode: Mapped[Optional[int]] = None # type: ignore - contract_size: Mapped[Optional[float]] = None # type: ignore + trading_mode: TradingMode = TradingMode.SPOT + amount_precision: Optional[float] = None + price_precision: Optional[float] = None + precision_mode: Optional[int] = None + contract_size: Optional[float] = None # Leverage trading properties - liquidation_price: Mapped[Optional[float]] = None # type: ignore - is_short: Mapped[bool] = False # type: ignore - leverage: Mapped[float] = 1.0 # type: ignore + liquidation_price: Optional[float] = None + is_short: bool = False + leverage: float = 1.0 # Margin trading properties - interest_rate: Mapped[float] = 0.0 # type: ignore + interest_rate: float = 0.0 # Futures properties - funding_fees: Mapped[Optional[float]] = None # type: ignore + funding_fees: Optional[float] = None @property def stoploss_or_liquidation(self) -> float: @@ -1179,77 +1179,93 @@ class Trade(ModelBase, LocalTrade): use_db: bool = True - id: Mapped[int] = mapped_column(Integer, primary_key=True) + id: Mapped[int] = mapped_column(Integer, primary_key=True) # type: ignore orders: Mapped[List[Order]] = relationship( "Order", order_by="Order.id", cascade="all, delete-orphan", lazy="selectin", - innerjoin=True) + innerjoin=True) # type: ignore - exchange: Mapped[str] = mapped_column(String(25), nullable=False) - pair: Mapped[str] = mapped_column(String(25), nullable=False, index=True) - base_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) - stake_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) - is_open: Mapped[bool] = mapped_column(nullable=False, default=True, index=True) - fee_open: Mapped[float] = mapped_column(Float(), nullable=False, default=0.0) - fee_open_cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) - fee_open_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) - fee_close: Mapped[Optional[float]] = mapped_column(Float(), nullable=False, default=0.0) - fee_close_cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) - fee_close_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) - open_rate: Mapped[float] = mapped_column(Float()) - open_rate_requested: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + exchange: Mapped[str] = mapped_column(String(25), nullable=False) # type: ignore + pair: Mapped[str] = mapped_column(String(25), nullable=False, index=True) # type: ignore + base_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) # type: ignore + stake_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) # type: ignore + is_open: Mapped[bool] = mapped_column(nullable=False, default=True, index=True) # type: ignore + fee_open: Mapped[float] = mapped_column(Float(), nullable=False, default=0.0) # type: ignore + fee_open_cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore + fee_open_currency: Mapped[Optional[str]] = mapped_column( + String(25), nullable=True) # type: ignore + fee_close: Mapped[Optional[float]] = mapped_column( + Float(), nullable=False, default=0.0) # type: ignore + fee_close_cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore + fee_close_currency: Mapped[Optional[str]] = mapped_column( + String(25), nullable=True) # type: ignore + open_rate: Mapped[float] = mapped_column(Float()) # type: ignore + open_rate_requested: Mapped[Optional[float]] = mapped_column( + Float(), nullable=True) # type: ignore # open_trade_value - calculated via _calc_open_trade_value - open_trade_value = mapped_column(Float()) - close_rate: Mapped[Optional[float]] = mapped_column(Float()) - close_rate_requested: Mapped[Optional[float]] = mapped_column(Float()) - realized_profit: Mapped[float] = mapped_column(Float(), default=0.0) - close_profit: Mapped[Optional[float]] = mapped_column(Float()) - close_profit_abs: Mapped[Optional[float]] = mapped_column(Float()) - stake_amount: Mapped[float] = mapped_column(Float(), nullable=False) - max_stake_amount: Mapped[Optional[float]] = mapped_column(Float()) - amount: Mapped[float] = mapped_column(Float()) - amount_requested: Mapped[Optional[float]] = mapped_column(Float()) - open_date: Mapped[datetime] = mapped_column(nullable=False, default=datetime.utcnow) - close_date: Mapped[Optional[datetime]] = mapped_column() - open_order_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + open_trade_value: Mapped[float] = mapped_column(Float(), nullable=True) # type: ignore + close_rate: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore + close_rate_requested: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore + realized_profit: Mapped[float] = mapped_column( + Float(), default=0.0, nullable=True) # type: ignore + close_profit: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore + close_profit_abs: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore + stake_amount: Mapped[float] = mapped_column(Float(), nullable=False) # type: ignore + max_stake_amount: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore + amount: Mapped[float] = mapped_column(Float()) # type: ignore + amount_requested: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore + open_date: Mapped[datetime] = mapped_column( + nullable=False, default=datetime.utcnow) # type: ignore + close_date: Mapped[Optional[datetime]] = mapped_column() # type: ignore + open_order_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) # type: ignore # absolute value of the stop loss - stop_loss: Mapped[float] = mapped_column(Float(), nullable=True, default=0.0) + stop_loss: Mapped[float] = mapped_column(Float(), nullable=True, default=0.0) # type: ignore # percentage value of the stop loss - stop_loss_pct: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + stop_loss_pct: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore # absolute value of the initial stop loss - initial_stop_loss: Mapped[Optional[float]] = mapped_column(Float(), nullable=True, default=0.0) + initial_stop_loss: Mapped[Optional[float]] = mapped_column( + Float(), nullable=True, default=0.0) # type: ignore # percentage value of the initial stop loss - initial_stop_loss_pct: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + initial_stop_loss_pct: Mapped[Optional[float]] = mapped_column( + Float(), nullable=True) # type: ignore # stoploss order id which is on exchange - stoploss_order_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True, index=True) + stoploss_order_id: Mapped[Optional[str]] = mapped_column( + String(255), nullable=True, index=True) # type: ignore # last update time of the stoploss order on exchange - stoploss_last_update: Mapped[Optional[datetime]] = mapped_column(nullable=True) + stoploss_last_update: Mapped[Optional[datetime]] = mapped_column(nullable=True) # type: ignore # absolute value of the highest reached price - max_rate: Mapped[Optional[float]] = mapped_column(Float(), nullable=True, default=0.0) + max_rate: Mapped[Optional[float]] = mapped_column( + Float(), nullable=True, default=0.0) # type: ignore # Lowest price reached - min_rate: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) - exit_reason: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) - exit_order_status: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) - strategy: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) - enter_tag: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) - timeframe: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) + min_rate: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore + exit_reason: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # type: ignore + exit_order_status: Mapped[Optional[str]] = mapped_column( + String(100), nullable=True) # type: ignore + strategy: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # type: ignore + enter_tag: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # type: ignore + timeframe: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore - trading_mode: Mapped[TradingMode] = mapped_column(Enum(TradingMode), nullable=True) - amount_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) - price_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) - precision_mode: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) - contract_size: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + trading_mode: Mapped[TradingMode] = mapped_column( + Enum(TradingMode), nullable=True) # type: ignore + amount_precision: Mapped[Optional[float]] = mapped_column( + Float(), nullable=True) # type: ignore + price_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore + precision_mode: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore + contract_size: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore # Leverage trading properties - leverage: Mapped[float] = mapped_column(Float(), nullable=True, default=1.0) - is_short: Mapped[bool] = mapped_column(nullable=False, default=False) - liquidation_price: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + leverage: Mapped[float] = mapped_column(Float(), nullable=True, default=1.0) # type: ignore + is_short: Mapped[bool] = mapped_column(nullable=False, default=False) # type: ignore + liquidation_price: Mapped[Optional[float]] = mapped_column( + Float(), nullable=True) # type: ignore # Margin Trading Properties - interest_rate: Mapped[float] = mapped_column(Float(), nullable=False, default=0.0) + interest_rate: Mapped[float] = mapped_column( + Float(), nullable=False, default=0.0) # type: ignore # Futures properties - funding_fees: Mapped[Optional[float]] = mapped_column(Float(), nullable=True, default=None) + funding_fees: Mapped[Optional[float]] = mapped_column( + Float(), nullable=True, default=None) # type: ignore def __init__(self, **kwargs): super().__init__(**kwargs) From 59d57d34667524e3eeed404fbfb0e3c62b4faa3b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Mar 2023 20:01:43 +0100 Subject: [PATCH 172/186] Improve test resiliance --- tests/test_freqtradebot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 1ea3ebfc6..800e76342 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3036,6 +3036,7 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_ # Order remained open for some reason (cancel failed) cancel_buy_order['status'] = 'open' cancel_order_mock = MagicMock(return_value=cancel_buy_order) + trade.open_order_id = 'some_open_order' mocker.patch(f'{EXMS}.cancel_order_with_result', cancel_order_mock) assert not freqtrade.handle_cancel_enter(trade, l_order, reason) assert log_has_re(r"Order .* for .* not cancelled.", caplog) @@ -3231,6 +3232,7 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None: trade = MagicMock() reason = CANCEL_REASON['TIMEOUT'] order = {'remaining': 1, + 'id': '125', 'amount': 1, 'status': "open"} assert not freqtrade.handle_cancel_exit(trade, order, reason) From b4b8dde4fb9a0646297c17af8175f5f1cc3d70af Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Mar 2023 20:41:49 +0100 Subject: [PATCH 173/186] Add sqlalchemy to pre-commit dependencies --- .pre-commit-config.yaml | 1 + build_helpers/pre_commit_update.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 05f4df92b..acc0c8a17 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,6 +18,7 @@ repos: - types-requests==2.28.11.15 - types-tabulate==0.9.0.1 - types-python-dateutil==2.8.19.9 + - SQLAlchemy==2.0.4 # stages: [push] - repo: https://github.com/pycqa/isort diff --git a/build_helpers/pre_commit_update.py b/build_helpers/pre_commit_update.py index 8724d8ade..e6b47d100 100644 --- a/build_helpers/pre_commit_update.py +++ b/build_helpers/pre_commit_update.py @@ -8,12 +8,17 @@ import yaml pre_commit_file = Path('.pre-commit-config.yaml') require_dev = Path('requirements-dev.txt') +require = Path('requirements.txt') with require_dev.open('r') as rfile: requirements = rfile.readlines() +with require.open('r') as rfile: + requirements.extend(rfile.readlines()) + # Extract types only -type_reqs = [r.strip('\n') for r in requirements if r.startswith('types-')] +type_reqs = [r.strip('\n') for r in requirements if r.startswith( + 'types-') or r.startswith('SQLAlchemy')] with pre_commit_file.open('r') as file: f = yaml.load(file, Loader=yaml.FullLoader) From b980f45b2b3bbb5ef1b381af9708694f9d1685d3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 2 Mar 2023 06:23:01 +0100 Subject: [PATCH 174/186] Fix test mypy errors --- freqtrade/persistence/trade_model.py | 4 ++++ .../strats/broken_strats/broken_futures_strategies.py | 5 +++-- tests/strategy/strats/strategy_test_v3.py | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 62e2e2a7f..a05eb7409 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -99,6 +99,10 @@ class Order(ModelBase): def safe_filled(self) -> float: return self.filled if self.filled is not None else self.amount or 0.0 + @property + def safe_cost(self) -> float: + return self.cost or 0.0 + @property def safe_remaining(self) -> float: return ( diff --git a/tests/strategy/strats/broken_strats/broken_futures_strategies.py b/tests/strategy/strats/broken_strats/broken_futures_strategies.py index 7e6955d37..bb7ce2b32 100644 --- a/tests/strategy/strats/broken_strats/broken_futures_strategies.py +++ b/tests/strategy/strats/broken_strats/broken_futures_strategies.py @@ -7,6 +7,7 @@ from datetime import datetime from pandas import DataFrame +from freqtrade.persistence.trade_model import Order from freqtrade.strategy.interface import IStrategy @@ -35,7 +36,7 @@ class TestStrategyImplementBuyTimeout(TestStrategyNoImplementSell): def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: return super().populate_exit_trend(dataframe, metadata) - def check_buy_timeout(self, pair: str, trade, order: dict, + def check_buy_timeout(self, pair: str, trade, order: Order, current_time: datetime, **kwargs) -> bool: return False @@ -44,6 +45,6 @@ class TestStrategyImplementSellTimeout(TestStrategyNoImplementSell): def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: return super().populate_exit_trend(dataframe, metadata) - def check_sell_timeout(self, pair: str, trade, order: dict, + def check_sell_timeout(self, pair: str, trade, order: Order, current_time: datetime, **kwargs) -> bool: return False diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py index 6f5ff573b..2d5121403 100644 --- a/tests/strategy/strats/strategy_test_v3.py +++ b/tests/strategy/strats/strategy_test_v3.py @@ -197,7 +197,7 @@ class StrategyTestV3(IStrategy): if current_profit < -0.0075: orders = trade.select_filled_orders(trade.entry_side) - return round(orders[0].cost, 0) + return round(orders[0].safe_cost, 0) return None From 8103656ae17e4e851584f2f9146491faf7529bf9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 2 Mar 2023 06:36:03 +0100 Subject: [PATCH 175/186] Bump mypy in pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index acc0c8a17..565eb96f7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: # stages: [push] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v0.991" + rev: "v1.0.1" hooks: - id: mypy exclude: build_helpers From ba38a826e95bee9d33e3c9c692faf561adc04437 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 2 Mar 2023 06:46:17 +0100 Subject: [PATCH 176/186] Update missing mocks --- tests/exchange/test_exchange.py | 2 +- tests/test_freqtradebot.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 9bc176f41..940319a45 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -5241,7 +5241,7 @@ def test_get_liquidation_price( default_conf_usdt['trading_mode'] = trading_mode default_conf_usdt['exchange']['name'] = exchange_name default_conf_usdt['margin_mode'] = margin_mode - mocker.patch('freqtrade.exchange.Gate.validate_ordertypes') + mocker.patch('freqtrade.exchange.gate.Gate.validate_ordertypes') exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name) exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01)) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 1ea3ebfc6..e2fb1618b 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -786,7 +786,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, default_conf_usdt['exchange']['name'] = exchange_name if margin_mode: default_conf_usdt['margin_mode'] = margin_mode - mocker.patch('freqtrade.exchange.Gate.validate_ordertypes') + mocker.patch('freqtrade.exchange.gate.Gate.validate_ordertypes') patch_RPCManager(mocker) patch_exchange(mocker, id=exchange_name) freqtrade = FreqtradeBot(default_conf_usdt) @@ -814,7 +814,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, get_max_leverage=MagicMock(return_value=10), ) mocker.patch.multiple( - 'freqtrade.exchange.Okx', + 'freqtrade.exchange.okx.Okx', get_max_pair_stake_amount=MagicMock(return_value=500000), ) pair = 'ETH/USDT' From 103bd9e2f217bbf277153f9f0fb63f25b30246d0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 2 Mar 2023 06:55:33 +0100 Subject: [PATCH 177/186] keep Trade.session private --- freqtrade/commands/db_commands.py | 2 +- freqtrade/persistence/models.py | 12 ++++++------ freqtrade/persistence/pairlock.py | 2 +- freqtrade/persistence/trade_model.py | 4 ++-- tests/persistence/test_migrations.py | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/freqtrade/commands/db_commands.py b/freqtrade/commands/db_commands.py index b4997582d..c424016b1 100644 --- a/freqtrade/commands/db_commands.py +++ b/freqtrade/commands/db_commands.py @@ -20,7 +20,7 @@ def start_convert_db(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) init_db(config['db_url']) - session_target = Trade.session + session_target = Trade._session init_db(config['db_url_from']) logger.info("Starting db migration.") diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index f4058b4eb..d718af2f4 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -54,12 +54,12 @@ def init_db(db_url: str) -> None: # https://docs.sqlalchemy.org/en/13/orm/contextual.html#thread-local-scope # Scoped sessions proxy requests to the appropriate thread-local session. # We should use the scoped_session object - not a seperately initialized version - Trade.session = scoped_session(sessionmaker(bind=engine, autoflush=False)) - Order.session = Trade.session - PairLock.session = Trade.session - Trade.query = Trade.session.query_property() - Order.query = Trade.session.query_property() - PairLock.query = Trade.session.query_property() + Trade._session = scoped_session(sessionmaker(bind=engine, autoflush=False)) + Order._session = Trade._session + PairLock._session = Trade._session + Trade.query = Trade._session.query_property() + Order.query = Trade._session.query_property() + PairLock.query = Trade._session.query_property() previous_tables = inspect(engine).get_table_names() ModelBase.metadata.create_all(engine) diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py index d5a8d7ae1..a6d1eeaf0 100644 --- a/freqtrade/persistence/pairlock.py +++ b/freqtrade/persistence/pairlock.py @@ -15,7 +15,7 @@ class PairLock(ModelBase): """ __tablename__ = 'pairlocks' query: ClassVar[_QueryDescriptorType] - session: ClassVar[SessionType] + _session: ClassVar[SessionType] id: Mapped[int] = mapped_column(primary_key=True) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index a05eb7409..0ae5fba25 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -37,7 +37,7 @@ class Order(ModelBase): """ __tablename__ = 'orders' query: ClassVar[_QueryDescriptorType] - session: ClassVar[SessionType] + _session: ClassVar[SessionType] # Uniqueness should be ensured over pair, order_id # its likely that order_id is unique per Pair on some exchanges. @@ -1179,7 +1179,7 @@ class Trade(ModelBase, LocalTrade): """ __tablename__ = 'trades' query: ClassVar[_QueryDescriptorType] - session: ClassVar[SessionType] + _session: ClassVar[SessionType] use_db: bool = True diff --git a/tests/persistence/test_migrations.py b/tests/persistence/test_migrations.py index 5254164c1..2a6959d58 100644 --- a/tests/persistence/test_migrations.py +++ b/tests/persistence/test_migrations.py @@ -21,8 +21,8 @@ spot, margin, futures = TradingMode.SPOT, TradingMode.MARGIN, TradingMode.FUTURE def test_init_create_session(default_conf): # Check if init create a session init_db(default_conf['db_url']) - assert hasattr(Trade, 'session') - assert 'scoped_session' in type(Trade.session).__name__ + assert hasattr(Trade, '_session') + assert 'scoped_session' in type(Trade._session).__name__ def test_init_custom_db_url(default_conf, tmpdir): @@ -34,7 +34,7 @@ def test_init_custom_db_url(default_conf, tmpdir): init_db(default_conf['db_url']) assert Path(filename).is_file() - r = Trade.session.execute(text("PRAGMA journal_mode")) + r = Trade._session.execute(text("PRAGMA journal_mode")) assert r.first() == ('wal',) From 49bfa556bfa413833fbb59b6bde2e3de6de9f65f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Oct 2022 21:04:40 +0200 Subject: [PATCH 178/186] Update CI to test against python 3.11 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17c0efd6d..191a10d1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: strategy: matrix: os: [ ubuntu-20.04, ubuntu-22.04 ] - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 @@ -115,7 +115,7 @@ jobs: strategy: matrix: os: [ macos-latest ] - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 @@ -212,7 +212,7 @@ jobs: strategy: matrix: os: [ windows-latest ] - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 From 684d310ea0d7c05996fe08b6e509cc1d6771c61e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 13:20:26 +0100 Subject: [PATCH 179/186] Limit catboost to python <3.11 --- requirements-freqai.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index 5b27ecf95..bc0be85e5 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -5,7 +5,7 @@ # Required for freqai scikit-learn==1.1.3 joblib==1.2.0 -catboost==1.1.1; platform_machine != 'aarch64' +catboost==1.1.1; platform_machine != 'aarch64' and python_version < '3.11' lightgbm==3.3.5 xgboost==1.7.4 tensorboard==2.12.0 From 7a7f16b6589fc2b36ceb0bd17651405bb8f5a4ce Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 13:27:28 +0100 Subject: [PATCH 180/186] Skip catboost tests on py3.11 --- tests/freqai/test_freqai_interface.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index cdfc943af..7d229434f 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -1,5 +1,6 @@ import platform import shutil +import sys from pathlib import Path from unittest.mock import MagicMock @@ -17,6 +18,10 @@ from tests.conftest import EXMS, create_mock_trades, get_patched_exchange, log_h from tests.freqai.conftest import get_patched_freqai_strategy, make_rl_config +def is_py11() -> bool: + return sys.version_info >= (3, 11) + + def is_arm() -> bool: machine = platform.machine() return "arm" in machine or "aarch64" in machine @@ -41,7 +46,7 @@ def is_mac() -> bool: def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, dbscan, float32, can_short, shuffle, buffer): - if is_arm() and model == 'CatboostRegressor': + if (is_arm() or is_py11()) and model == 'CatboostRegressor': pytest.skip("CatBoost is not supported on ARM") if is_mac() and not is_arm() and 'Reinforcement' in model: @@ -117,7 +122,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, ('CatboostClassifierMultiTarget', "freqai_test_multimodel_classifier_strat") ]) def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, strat): - if is_arm() and 'Catboost' in model: + if (is_arm() or is_py11()) and 'Catboost' in model: pytest.skip("CatBoost is not supported on ARM") freqai_conf.update({"timerange": "20180110-20180130"}) @@ -159,7 +164,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s 'XGBoostRFClassifier', ]) def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model): - if is_arm() and model == 'CatboostClassifier': + if (is_arm() or is_py11()) and model == 'CatboostClassifier': pytest.skip("CatBoost is not supported on ARM") freqai_conf.update({"freqaimodel": model}) @@ -208,7 +213,7 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model): def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog): freqai_conf.get("freqai", {}).update({"save_backtest_models": True}) freqai_conf['runmode'] = RunMode.BACKTEST - if is_arm() and "Catboost" in model: + if (is_arm() or is_py11()) and "Catboost" in model: pytest.skip("CatBoost is not supported on ARM") if is_mac() and 'Reinforcement' in model: From b1a5776f1480461f87403e136df4eb591a3b0c97 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 15:21:12 +0100 Subject: [PATCH 181/186] Skip reinforcement learning for python 3.11 --- requirements-freqai-rl.txt | 8 ++++---- tests/freqai/test_freqai_interface.py | 7 ++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt index c242af43e..4de7d8fab 100644 --- a/requirements-freqai-rl.txt +++ b/requirements-freqai-rl.txt @@ -2,9 +2,9 @@ -r requirements-freqai.txt # Required for freqai-rl -torch==1.13.1 -stable-baselines3==1.7.0 -sb3-contrib==1.7.0 +torch==1.13.1; python_version < '3.11' +stable-baselines3==1.7.0; python_version < '3.11' +sb3-contrib==1.7.0; python_version < '3.11' # Gym is forced to this version by stable-baselines3. setuptools==65.5.1 # Should be removed when gym is fixed. -gym==0.21 +gym==0.21; python_version < '3.11' diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index 7d229434f..0584ed9f5 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -51,7 +51,8 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, if is_mac() and not is_arm() and 'Reinforcement' in model: pytest.skip("Reinforcement learning module not available on intel based Mac OS") - + if is_py11() and 'Reinforcement' in model: + pytest.skip("Reinforcement learning currently not available on python 3.11.") model_save_ext = 'joblib' freqai_conf.update({"freqaimodel": model}) freqai_conf.update({"timerange": "20180110-20180130"}) @@ -218,6 +219,8 @@ def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog) if is_mac() and 'Reinforcement' in model: pytest.skip("Reinforcement learning module not available on intel based Mac OS") + if is_py11() and 'Reinforcement' in model: + pytest.skip("Reinforcement learning currently not available on python 3.11.") Trade.use_db = False freqai_conf.update({"freqaimodel": model}) @@ -514,6 +517,8 @@ def test_get_state_info(mocker, freqai_conf, dp_exists, caplog, tickers): if is_mac(): pytest.skip("Reinforcement learning module not available on intel based Mac OS") + if is_py11(): + pytest.skip("Reinforcement learning currently not available on python 3.11.") freqai_conf.update({"freqaimodel": "ReinforcementLearner"}) freqai_conf.update({"timerange": "20180110-20180130"}) From 38050b5346fa563e9592bdb3258602adb1b753c1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Feb 2023 15:25:47 +0100 Subject: [PATCH 182/186] Simplify "model-run" conditions --- tests/freqai/test_freqai_interface.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index 0584ed9f5..f8bee3659 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -32,6 +32,17 @@ def is_mac() -> bool: return "Darwin" in machine +def can_run_model(model: str) -> None: + if (is_arm() or is_py11()) and "Catboost" in model: + pytest.skip("CatBoost is not supported on ARM") + + if is_mac() and not is_arm() and 'Reinforcement' in model: + pytest.skip("Reinforcement learning module not available on intel based Mac OS") + + if is_py11() and 'Reinforcement' in model: + pytest.skip("Reinforcement learning 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), @@ -46,13 +57,7 @@ def is_mac() -> bool: def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, dbscan, float32, can_short, shuffle, buffer): - if (is_arm() or is_py11()) and model == 'CatboostRegressor': - pytest.skip("CatBoost is not supported on ARM") - - if is_mac() and not is_arm() and 'Reinforcement' in model: - pytest.skip("Reinforcement learning module not available on intel based Mac OS") - if is_py11() and 'Reinforcement' in model: - pytest.skip("Reinforcement learning currently not available on python 3.11.") + can_run_model(model) model_save_ext = 'joblib' freqai_conf.update({"freqaimodel": model}) freqai_conf.update({"timerange": "20180110-20180130"}) @@ -212,15 +217,11 @@ 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) + freqai_conf.get("freqai", {}).update({"save_backtest_models": True}) freqai_conf['runmode'] = RunMode.BACKTEST - if (is_arm() or is_py11()) and "Catboost" in model: - pytest.skip("CatBoost is not supported on ARM") - if is_mac() and 'Reinforcement' in model: - pytest.skip("Reinforcement learning module not available on intel based Mac OS") - if is_py11() and 'Reinforcement' in model: - pytest.skip("Reinforcement learning currently not available on python 3.11.") Trade.use_db = False freqai_conf.update({"freqaimodel": model}) From 5d0e14b5644d100ed2a5fe7ddcc6b987cbc69741 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 2 Mar 2023 07:07:09 +0100 Subject: [PATCH 183/186] Don't mock full modules --- tests/rpc/test_rpc_apiserver.py | 4 ++-- tests/rpc/test_rpc_telegram.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index b104ec854..f898dd476 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1179,7 +1179,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): ftbot.config['force_entry_enable'] = True fbuy_mock = MagicMock(return_value=None) - mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock) + mocker.patch("freqtrade.rpc.rpc.RPC._rpc_force_entry", fbuy_mock) rc = client_post(client, f"{BASE_URI}/{endpoint}", data={"pair": "ETH/BTC"}) assert_response(rc) @@ -1205,7 +1205,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): strategy=CURRENT_TEST_STRATEGY, trading_mode=TradingMode.SPOT )) - mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock) + mocker.patch("freqtrade.rpc.rpc.RPC._rpc_force_entry", fbuy_mock) rc = client_post(client, f"{BASE_URI}/{endpoint}", data={"pair": "ETH/BTC"}) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 3e1421cb5..7b3411113 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1209,7 +1209,7 @@ def test_force_enter_handle(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) fbuy_mock = MagicMock(return_value=None) - mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock) + mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_entry', fbuy_mock) telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -1226,7 +1226,7 @@ def test_force_enter_handle(default_conf, update, mocker) -> None: # Reset and retry with specified price fbuy_mock = MagicMock(return_value=None) - mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock) + mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_entry', fbuy_mock) # /forcelong ETH/BTC 0.055 context = MagicMock() context.args = ["ETH/BTC", "0.055"] @@ -1255,7 +1255,7 @@ def test_force_enter_no_pair(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) fbuy_mock = MagicMock(return_value=None) - mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock) + mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_entry', fbuy_mock) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) From 5b0c1437130a502f322e915a4095b43e68ba88b2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 2 Mar 2023 19:29:41 +0100 Subject: [PATCH 184/186] Update some comments about 3.11 --- docs/freqai.md | 4 ++++ docs/windows_installation.md | 2 +- setup.cfg | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/freqai.md b/docs/freqai.md index 5c6b5b2ce..ef8efb840 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -71,6 +71,10 @@ pip install -r requirements-freqai.txt !!! Note Catboost will not be installed on arm devices (raspberry, Mac M1, ARM based VPS, ...), since it does not provide wheels for this platform. +!!! Note "python 3.11" + Some dependencies (Catboost, Torch) currently don't support python 3.11. Freqtrade therefore only supports python 3.10 for these models/dependencies. + Tests involving these dependencies are skipped on 3.11. + ### Usage with docker If you are using docker, a dedicated tag with FreqAI dependencies is available as `:freqai`. As such - you can replace the image line in your docker compose file with `image: freqtradeorg/freqtrade:develop_freqai`. This image contains the regular FreqAI dependencies. Similar to native installs, Catboost will not be available on ARM based devices. diff --git a/docs/windows_installation.md b/docs/windows_installation.md index 1b0d9d724..43d6728ee 100644 --- a/docs/windows_installation.md +++ b/docs/windows_installation.md @@ -26,7 +26,7 @@ Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7 As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which need to be downloaded and installed using `pip install TA_Lib-0.4.25-cp38-cp38-win_amd64.whl` (make sure to use the version matching your python version). -Freqtrade provides these dependencies for the latest 3 Python versions (3.8, 3.9 and 3.10) and for 64bit Windows. +Freqtrade provides these dependencies for the latest 3 Python versions (3.8, 3.9, 3.10 and 3.11) and for 64bit Windows. Other versions must be downloaded from the above link. ``` powershell diff --git a/setup.cfg b/setup.cfg index 60ec8a75f..b54b62619 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,7 @@ classifiers = Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Operating System :: MacOS Operating System :: Unix Topic :: Office/Business :: Financial :: Investment From 022f85095e2100901d1457d1014fc879ae033843 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 2 Mar 2023 19:50:01 +0100 Subject: [PATCH 185/186] Show Number of exits part of #8234 --- freqtrade/rpc/telegram.py | 3 +++ tests/rpc/test_rpc_telegram.py | 1 + 2 files changed, 4 insertions(+) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index dc92478ab..6f82a7316 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -561,6 +561,8 @@ class Telegram(RPCHandler): for r in results: r['open_date_hum'] = arrow.get(r['open_date']).humanize() r['num_entries'] = len([o for o in r['orders'] if o['ft_is_entry']]) + r['num_exits'] = len([o for o in r['orders'] if not o['ft_is_entry'] + and not o['ft_order_side'] == 'stoploss']) r['exit_reason'] = r.get('exit_reason', "") r['stake_amount_r'] = round_coin_value(r['stake_amount'], r['quote_currency']) r['profit_abs_r'] = round_coin_value(r['profit_abs'], r['quote_currency']) @@ -581,6 +583,7 @@ class Telegram(RPCHandler): if position_adjust: max_buy_str = (f"/{max_entries + 1}" if (max_entries > 0) else "") lines.append("*Number of Entries:* `{num_entries}`" + max_buy_str) + lines.append("*Number of Exits:* `{num_exits}`") lines.extend([ "*Open Rate:* `{open_rate:.8f}`", diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 7b3411113..26cb93821 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -279,6 +279,7 @@ def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None: assert msg_mock.call_count == 4 msg = msg_mock.call_args_list[0][0][0] assert re.search(r'Number of Entries.*2', msg) + assert re.search(r'Number of Exits.*0', msg) assert re.search(r'Average Entry Price', msg) assert re.search(r'Order filled', msg) assert re.search(r'Close Date:', msg) is None From 9573974c4798982a87c9e70a7a341ac4235ba956 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 3 Mar 2023 06:36:35 +0100 Subject: [PATCH 186/186] Update deprecations document --- docs/deprecated.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/deprecated.md b/docs/deprecated.md index 3b5b28b81..6719ce56d 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -74,3 +74,8 @@ Webhook terminology changed from "sell" to "exit", and from "buy" to "entry", re * `webhooksell`, `webhookexit` -> `exit` * `webhooksellfill`, `webhookexitfill` -> `exit_fill` * `webhooksellcancel`, `webhookexitcancel` -> `exit_cancel` + + +## Removal of `populate_any_indicators` + +version 2023.3 saw the removal of `populate_any_indicators` in favor of split methods for feature engineering and targets. Please read the [migration document](strategy_migration.md#freqai-strategy) for full details.