From 3ccc120f924b9fec07a08bdf0e3794b27b557e97 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Fri, 4 Nov 2022 17:42:10 +0100 Subject: [PATCH 01/41] add option to force single precision --- freqtrade/freqai/data_kitchen.py | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index e06709b2c..1e3a518d0 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -1246,6 +1246,9 @@ class FreqaiDataKitchen: self.get_unique_classes_from_labels(dataframe) + if self.freqai_config.get('convert_df_to_float32', False): + dataframe = self.reduce_dataframe_footprint(dataframe) + return dataframe def fit_labels(self) -> None: @@ -1344,3 +1347,35 @@ class FreqaiDataKitchen: f"Could not find backtesting prediction file at {path_to_predictionfile}" ) return False + + def reduce_dataframe_footprint(self, df: DataFrame) -> DataFrame: + """ + Ensure all values are float32 + """ + start_mem = df.memory_usage().sum() / 1024**2 + print("Memory usage of dataframe is {:.2f} MB".format(start_mem)) + + for col in df.columns[1:]: + col_type = df[col].dtype + + if col_type != object: + c_min = df[col].min() + c_max = df[col].max() + if str(col_type)[:3] == "int": + if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max: + df[col] = df[col].astype(np.int8) + elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max: + df[col] = df[col].astype(np.int16) + elif c_min > np.iinfo(np.int32).min: + df[col] = df[col].astype(np.int32) + else: + if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max: + df[col] = df[col].astype(np.float16) + elif c_min > np.finfo(np.float32).min: + df[col] = df[col].astype(np.float32) + + end_mem = df.memory_usage().sum() / 1024**2 + print("Memory usage after optimization is: {:.2f} MB".format(end_mem)) + print("Decreased by {:.1f}%".format(100 * (start_mem - end_mem) / start_mem)) + + return df From 257c83383146f430d334839b6958a94dcd56657b Mon Sep 17 00:00:00 2001 From: robcaulk Date: Fri, 4 Nov 2022 18:10:04 +0100 Subject: [PATCH 02/41] add doc for single precision, dont allow half precision, add test --- docs/freqai-parameter-table.md | 1 + freqtrade/freqai/data_kitchen.py | 15 ++------------- tests/freqai/test_freqai_interface.py | 13 +++++++------ 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/docs/freqai-parameter-table.md b/docs/freqai-parameter-table.md index 28a15913b..f4fbcbf1c 100644 --- a/docs/freqai-parameter-table.md +++ b/docs/freqai-parameter-table.md @@ -18,6 +18,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | `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. | `follow_mode` | Use a `follower` that will look for models associated with a specific `identifier` and load those for inferencing. A `follower` will **not** train new models.
**Datatype:** Boolean.
Default: `False`. | `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`. +| `convert_df_to_float32` | Recast all numeric columns to float32, with the objective of reducing ram/disk usage and decreasing train/inference timing.
**Datatype:** Boolean.
Default: `False`. | | **Feature parameters** | `feature_parameters` | A dictionary containing the parameters used to engineer the feature set. Details and examples are shown [here](freqai-feature-engineering.md).
**Datatype:** Dictionary. | `include_timeframes` | A list of timeframes that all indicators in `populate_any_indicators` will be created for. The list is added as features to the base indicators dataset.
**Datatype:** List of timeframes (strings). diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 1e3a518d0..f1c1fa26d 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -1357,22 +1357,11 @@ class FreqaiDataKitchen: for col in df.columns[1:]: col_type = df[col].dtype - if col_type != object: - c_min = df[col].min() - c_max = df[col].max() if str(col_type)[:3] == "int": - if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max: - df[col] = df[col].astype(np.int8) - elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max: - df[col] = df[col].astype(np.int16) - elif c_min > np.iinfo(np.int32).min: - df[col] = df[col].astype(np.int32) + df[col] = df[col].astype(np.int32) else: - if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max: - df[col] = df[col].astype(np.float16) - elif c_min > np.finfo(np.float32).min: - df[col] = df[col].astype(np.float32) + df[col] = df[col].astype(np.float32) end_mem = df.memory_usage().sum() / 1024**2 print("Memory usage after optimization is: {:.2f} MB".format(end_mem)) diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index 2bc65d52e..a2eed92e3 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -27,13 +27,13 @@ def is_mac() -> bool: return "Darwin" in machine -@pytest.mark.parametrize('model, pca, dbscan', [ - ('LightGBMRegressor', True, False), - ('XGBoostRegressor', False, True), - ('XGBoostRFRegressor', False, False), - ('CatboostRegressor', False, False), +@pytest.mark.parametrize('model, pca, dbscan, float32', [ + ('LightGBMRegressor', True, False, True), + ('XGBoostRegressor', False, True, False), + ('XGBoostRFRegressor', False, False, False), + ('CatboostRegressor', False, False, False), ]) -def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, dbscan): +def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, dbscan, float32): if is_arm() and model == 'CatboostRegressor': pytest.skip("CatBoost is not supported on ARM") @@ -43,6 +43,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, freqai_conf.update({"strategy": "freqai_test_strat"}) freqai_conf['freqai']['feature_parameters'].update({"principal_component_analysis": pca}) freqai_conf['freqai']['feature_parameters'].update({"use_DBSCAN_to_remove_outliers": dbscan}) + freqai_conf['freqai'].update({"convert_df_to_float32": float32}) strategy = get_patched_freqai_strategy(mocker, freqai_conf) exchange = get_patched_exchange(mocker, freqai_conf) From 43bdd34964976578acbc99a6f0b7f70f1bef8bbc Mon Sep 17 00:00:00 2001 From: Emre Date: Sat, 5 Nov 2022 19:13:02 +0300 Subject: [PATCH 03/41] Optimize reduce_dataframe_footprint function --- freqtrade/freqai/data_kitchen.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index f1c1fa26d..6b4586adc 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -1355,13 +1355,15 @@ class FreqaiDataKitchen: start_mem = df.memory_usage().sum() / 1024**2 print("Memory usage of dataframe is {:.2f} MB".format(start_mem)) - for col in df.columns[1:]: - col_type = df[col].dtype - if col_type != object: - if str(col_type)[:3] == "int": - df[col] = df[col].astype(np.int32) - else: - df[col] = df[col].astype(np.float32) + df_dtypes = df.dtypes + for column, dtype in df_dtypes.items(): + if column in ['open', 'high', 'low', 'close', 'volume']: + continue + if dtype == np.float64: + df_dtypes[column] = np.float32 + elif dtype == np.int64: + df_dtypes[column] = np.int32 + df = df.astype(df_dtypes) end_mem = df.memory_usage().sum() / 1024**2 print("Memory usage after optimization is: {:.2f} MB".format(end_mem)) From 3e676dbaa42835a41dc18b4b526da0a9b25ab2a3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 10 Nov 2022 16:33:57 +0100 Subject: [PATCH 04/41] Add properties to simplify timerange handling --- freqtrade/configuration/timerange.py | 12 +++++++++++- tests/test_timerange.py | 7 +++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/freqtrade/configuration/timerange.py b/freqtrade/configuration/timerange.py index 6979c8cd1..e152a625c 100644 --- a/freqtrade/configuration/timerange.py +++ b/freqtrade/configuration/timerange.py @@ -3,7 +3,7 @@ This module contains the argument manager class """ import logging import re -from datetime import datetime +from datetime import datetime, timezone from typing import Optional import arrow @@ -29,6 +29,16 @@ class TimeRange: self.startts: int = startts self.stopts: int = stopts + @property + def startdt(self) -> Optional[datetime]: + if self.startts: + return datetime.fromtimestamp(self.startts, tz=timezone.utc) + + @property + def stopdt(self) -> Optional[datetime]: + if self.stopts: + return datetime.fromtimestamp(self.stopts, tz=timezone.utc) + def __eq__(self, other): """Override the default Equals behavior""" return (self.starttype == other.starttype and self.stoptype == other.stoptype diff --git a/tests/test_timerange.py b/tests/test_timerange.py index dcdaad09d..07fad5d68 100644 --- a/tests/test_timerange.py +++ b/tests/test_timerange.py @@ -1,4 +1,6 @@ # pragma pylint: disable=missing-docstring, C0103 +from datetime import datetime, timezone + import arrow import pytest @@ -18,6 +20,10 @@ def test_parse_timerange_incorrect(): assert TimeRange(None, 'date', 0, 1233360000) == TimeRange.parse_timerange('-1233360000') timerange = TimeRange.parse_timerange('1231006505-1233360000') assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange + assert isinstance(timerange.startdt, datetime) + assert isinstance(timerange.stopdt, datetime) + assert timerange.startdt == datetime.fromtimestamp(1231006505, tz=timezone.utc) + assert timerange.stopdt == datetime.fromtimestamp(1233360000, tz=timezone.utc) timerange = TimeRange.parse_timerange('1231006505000-1233360000000') assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange @@ -45,6 +51,7 @@ def test_subtract_start(): x = TimeRange(None, 'date', 0, 1438214400) x.subtract_start(300) assert not x.startts + assert not x.startdt x = TimeRange('date', None, 1274486400, 0) x.subtract_start(300) From 57313dd9611e159f90c5f74b9fcc771a77aa1c35 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 10 Nov 2022 18:11:39 +0100 Subject: [PATCH 05/41] Update some usages of timerange to new, simplified method --- freqtrade/configuration/timerange.py | 2 ++ freqtrade/data/converter.py | 7 ++----- freqtrade/data/history/history_utils.py | 6 +++--- freqtrade/data/history/idatahandler.py | 6 ++---- freqtrade/freqai/data_kitchen.py | 18 ++++++++---------- freqtrade/freqai/utils.py | 4 +--- 6 files changed, 18 insertions(+), 25 deletions(-) diff --git a/freqtrade/configuration/timerange.py b/freqtrade/configuration/timerange.py index e152a625c..8fcf95b45 100644 --- a/freqtrade/configuration/timerange.py +++ b/freqtrade/configuration/timerange.py @@ -33,11 +33,13 @@ class TimeRange: def startdt(self) -> Optional[datetime]: if self.startts: return datetime.fromtimestamp(self.startts, tz=timezone.utc) + return None @property def stopdt(self) -> Optional[datetime]: if self.stopts: return datetime.fromtimestamp(self.stopts, tz=timezone.utc) + return None def __eq__(self, other): """Override the default Equals behavior""" diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py index 98ed15489..718011eb6 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -3,7 +3,6 @@ Functions to convert data from one format to another """ import itertools import logging -from datetime import datetime, timezone from operator import itemgetter from typing import Dict, List @@ -137,11 +136,9 @@ def trim_dataframe(df: DataFrame, timerange, df_date_col: str = 'date', df = df.iloc[startup_candles:, :] else: if timerange.starttype == 'date': - start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc) - df = df.loc[df[df_date_col] >= start, :] + df = df.loc[df[df_date_col] >= timerange.startdt, :] if timerange.stoptype == 'date': - stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc) - df = df.loc[df[df_date_col] <= stop, :] + df = df.loc[df[df_date_col] <= timerange.stopdt, :] return df diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index 93534e919..9a206baa4 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -1,6 +1,6 @@ import logging import operator -from datetime import datetime, timezone +from datetime import datetime from pathlib import Path from typing import Dict, List, Optional, Tuple @@ -160,9 +160,9 @@ def _load_cached_data_for_updating( end = None if timerange: if timerange.starttype == 'date': - start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc) + start = timerange.startdt if timerange.stoptype == 'date': - end = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc) + end = timerange.stopdt # Intentionally don't pass timerange in - since we need to load the full dataset. data = data_handler.ohlcv_load(pair, timeframe=timeframe, diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py index b82d2055b..57441b4be 100644 --- a/freqtrade/data/history/idatahandler.py +++ b/freqtrade/data/history/idatahandler.py @@ -366,13 +366,11 @@ class IDataHandler(ABC): """ if timerange.starttype == 'date': - start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc) - if pairdata.iloc[0]['date'] > start: + if pairdata.iloc[0]['date'] > timerange.startdt: logger.warning(f"{pair}, {candle_type}, {timeframe}, " f"data starts at {pairdata.iloc[0]['date']:%Y-%m-%d %H:%M:%S}") if timerange.stoptype == 'date': - stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc) - if pairdata.iloc[-1]['date'] < stop: + if pairdata.iloc[-1]['date'] < timerange.stopdt: logger.warning(f"{pair}, {candle_type}, {timeframe}, " f"data ends at {pairdata.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}") diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 12a3cd519..5e1238884 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -432,8 +432,8 @@ class FreqaiDataKitchen: timerange_train.stopts = timerange_train.startts + train_period_days first = False - start = datetime.fromtimestamp(timerange_train.startts, tz=timezone.utc) - stop = datetime.fromtimestamp(timerange_train.stopts, tz=timezone.utc) + start = timerange_train.startdt + stop = timerange_train.stopdt tr_training_list.append(start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d")) tr_training_list_timerange.append(copy.deepcopy(timerange_train)) @@ -446,8 +446,8 @@ class FreqaiDataKitchen: if timerange_backtest.stopts > config_timerange.stopts: timerange_backtest.stopts = config_timerange.stopts - start = datetime.fromtimestamp(timerange_backtest.startts, tz=timezone.utc) - stop = datetime.fromtimestamp(timerange_backtest.stopts, tz=timezone.utc) + start = timerange_backtest.startdt + stop = timerange_backtest.stopdt tr_backtesting_list.append(start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d")) tr_backtesting_list_timerange.append(copy.deepcopy(timerange_backtest)) @@ -490,11 +490,9 @@ class FreqaiDataKitchen: it is sliced down to just the present training period. """ - start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc) - stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc) - df = df.loc[df["date"] >= start, :] + df = df.loc[df["date"] >= timerange.startdt, :] if not self.live: - df = df.loc[df["date"] < stop, :] + df = df.loc[df["date"] < timerange.stopdt, :] return df @@ -1057,8 +1055,8 @@ class FreqaiDataKitchen: backtest_timerange.startts = ( backtest_timerange.startts - backtest_period_days * SECONDS_IN_DAY ) - start = datetime.fromtimestamp(backtest_timerange.startts, tz=timezone.utc) - stop = datetime.fromtimestamp(backtest_timerange.stopts, tz=timezone.utc) + start = backtest_timerange.startdt + stop = backtest_timerange.stopdt full_timerange = start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d") config_path = Path(self.config["config_files"][0]) diff --git a/freqtrade/freqai/utils.py b/freqtrade/freqai/utils.py index e854bcf0b..aa5185075 100644 --- a/freqtrade/freqai/utils.py +++ b/freqtrade/freqai/utils.py @@ -230,7 +230,5 @@ def get_timerange_backtest_live_models(config: Config) -> str: dk = FreqaiDataKitchen(config) models_path = dk.get_full_models_path(config) timerange, _ = dk.get_timerange_and_assets_end_dates_from_ready_models(models_path) - start_date = datetime.fromtimestamp(timerange.startts, tz=timezone.utc) - end_date = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc) - tr = f"{start_date.strftime('%Y%m%d')}-{end_date.strftime('%Y%m%d')}" + tr = f"{timerange.startdt.strftime('%Y%m%d')}-{timerange.stopdt.strftime('%Y%m%d')}" return tr From 0f9c5f8d41f2ea2674bb6fe8f195f4d9df097f9c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 10 Nov 2022 18:26:14 +0100 Subject: [PATCH 06/41] Simplify timerange handling --- freqtrade/configuration/timerange.py | 35 ++++++++++++++++++++++++++++ freqtrade/freqai/data_kitchen.py | 12 +++------- freqtrade/freqai/freqai_interface.py | 23 ++++-------------- freqtrade/freqai/utils.py | 3 +-- freqtrade/optimize/backtesting.py | 3 +-- tests/test_timerange.py | 12 ++++++++-- 6 files changed, 55 insertions(+), 33 deletions(-) diff --git a/freqtrade/configuration/timerange.py b/freqtrade/configuration/timerange.py index 8fcf95b45..adc5e65df 100644 --- a/freqtrade/configuration/timerange.py +++ b/freqtrade/configuration/timerange.py @@ -8,6 +8,7 @@ from typing import Optional import arrow +from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.exceptions import OperationalException @@ -41,6 +42,40 @@ class TimeRange: return datetime.fromtimestamp(self.stopts, tz=timezone.utc) return None + @property + def timerange_str(self) -> str: + """ + Returns a string representation of the timerange as used by parse_timerange. + Follows the format yyyymmdd-yyyymmdd - leaving out the parts that are not set. + """ + start = '' + stop = '' + if startdt := self.startdt: + start = startdt.strftime('%Y%m%d') + if stopdt := self.stopdt: + stop = stopdt.strftime('%Y%m%d') + return f"{start}-{stop}" + + @property + def start_fmt(self) -> str: + """ + Returns a string representation of the start date + """ + val = 'unbounded' + if (startdt := self.startdt) is not None: + val = startdt.strftime(DATETIME_PRINT_FORMAT) + return val + + @property + def stop_fmt(self) -> str: + """ + Returns a string representation of the stop date + """ + val = 'unbounded' + if (stopdt := self.stopdt) is not None: + val = stopdt.strftime(DATETIME_PRINT_FORMAT) + return val + def __eq__(self, other): """Override the default Equals behavior""" return (self.starttype == other.starttype and self.stoptype == other.stoptype diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 5e1238884..87df7fba0 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -432,9 +432,7 @@ class FreqaiDataKitchen: timerange_train.stopts = timerange_train.startts + train_period_days first = False - start = timerange_train.startdt - stop = timerange_train.stopdt - tr_training_list.append(start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d")) + tr_training_list.append(timerange_train.timerange_str) tr_training_list_timerange.append(copy.deepcopy(timerange_train)) # associated backtest period @@ -446,9 +444,7 @@ class FreqaiDataKitchen: if timerange_backtest.stopts > config_timerange.stopts: timerange_backtest.stopts = config_timerange.stopts - start = timerange_backtest.startdt - stop = timerange_backtest.stopdt - tr_backtesting_list.append(start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d")) + tr_backtesting_list.append(timerange_backtest.timerange_str) tr_backtesting_list_timerange.append(copy.deepcopy(timerange_backtest)) # ensure we are predicting on exactly same amount of data as requested by user defined @@ -1055,9 +1051,7 @@ class FreqaiDataKitchen: backtest_timerange.startts = ( backtest_timerange.startts - backtest_period_days * SECONDS_IN_DAY ) - start = backtest_timerange.startdt - stop = backtest_timerange.stopdt - full_timerange = start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d") + full_timerange = backtest_timerange.timerange_str config_path = Path(self.config["config_files"][0]) if not self.full_path.is_dir(): diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index ae123f852..94d471d13 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -13,7 +13,7 @@ from numpy.typing import NDArray from pandas import DataFrame from freqtrade.configuration import TimeRange -from freqtrade.constants import DATETIME_PRINT_FORMAT, Config +from freqtrade.constants import Config from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_seconds @@ -788,14 +788,8 @@ class IFreqaiModel(ABC): :return: if the data exists or not """ if self.config.get("freqai_backtest_live_models", False) and len(dataframe_backtest) == 0: - tr_backtest_startts_str = datetime.fromtimestamp( - tr_backtest.startts, - tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT) - tr_backtest_stopts_str = datetime.fromtimestamp( - tr_backtest.stopts, - tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT) - logger.info(f"No data found for pair {pair} from {tr_backtest_startts_str} " - f" from {tr_backtest_startts_str} to {tr_backtest_stopts_str}. " + logger.info(f"No data found for pair {pair} from " + f"from { tr_backtest.start_fmt} to {tr_backtest.stop_fmt}. " "Probably more than one training within the same candle period.") return False return True @@ -810,18 +804,11 @@ class IFreqaiModel(ABC): :param pair: the current pair :param total_trains: total trains (total number of slides for the sliding window) """ - tr_train_startts_str = datetime.fromtimestamp( - tr_train.startts, - tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT) - tr_train_stopts_str = datetime.fromtimestamp( - tr_train.stopts, - tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT) - if not self.config.get("freqai_backtest_live_models", False): logger.info( f"Training {pair}, {self.pair_it}/{self.total_pairs} pairs" - f" from {tr_train_startts_str} " - f"to {tr_train_stopts_str}, {train_it}/{total_trains} " + f" from {tr_train.start_fmt} " + f"to {tr_train.stop_fmt}, {train_it}/{total_trains} " "trains" ) # Following methods which are overridden by user made prediction models. diff --git a/freqtrade/freqai/utils.py b/freqtrade/freqai/utils.py index aa5185075..b64859f9f 100644 --- a/freqtrade/freqai/utils.py +++ b/freqtrade/freqai/utils.py @@ -230,5 +230,4 @@ def get_timerange_backtest_live_models(config: Config) -> str: dk = FreqaiDataKitchen(config) models_path = dk.get_full_models_path(config) timerange, _ = dk.get_timerange_and_assets_end_dates_from_ready_models(models_path) - tr = f"{timerange.startdt.strftime('%Y%m%d')}-{timerange.stopdt.strftime('%Y%m%d')}" - return tr + return timerange.timerange_str diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 3436eac44..bb588119a 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1286,8 +1286,7 @@ class Backtesting: def _get_min_cached_backtest_date(self): min_backtest_date = None backtest_cache_age = self.config.get('backtest_cache', constants.BACKTEST_CACHE_DEFAULT) - if self.timerange.stopts == 0 or datetime.fromtimestamp( - self.timerange.stopts, tz=timezone.utc) > datetime.now(tz=timezone.utc): + if self.timerange.stopts == 0 or self.timerange.stopdt > datetime.now(tz=timezone.utc): logger.warning('Backtest result caching disabled due to use of open-ended timerange.') elif backtest_cache_age == 'day': min_backtest_date = datetime.now(tz=timezone.utc) - timedelta(days=1) diff --git a/tests/test_timerange.py b/tests/test_timerange.py index 07fad5d68..06ff1983a 100644 --- a/tests/test_timerange.py +++ b/tests/test_timerange.py @@ -10,10 +10,17 @@ from freqtrade.exceptions import OperationalException def test_parse_timerange_incorrect(): - assert TimeRange('date', None, 1274486400, 0) == TimeRange.parse_timerange('20100522-') - assert TimeRange(None, 'date', 0, 1274486400) == TimeRange.parse_timerange('-20100522') + timerange = TimeRange.parse_timerange('20100522-') + assert TimeRange('date', None, 1274486400, 0) == timerange + assert timerange.timerange_str == '20100522-' + timerange = TimeRange.parse_timerange('-20100522') + assert TimeRange(None, 'date', 0, 1274486400) == timerange + assert timerange.timerange_str == '-20100522' timerange = TimeRange.parse_timerange('20100522-20150730') assert timerange == TimeRange('date', 'date', 1274486400, 1438214400) + assert timerange.timerange_str == '20100522-20150730' + assert timerange.start_fmt == '2010-05-22 00:00:00' + assert timerange.stop_fmt == '2015-07-30 00:00:00' # Added test for unix timestamp - BTC genesis date assert TimeRange('date', None, 1231006505, 0) == TimeRange.parse_timerange('1231006505-') @@ -24,6 +31,7 @@ def test_parse_timerange_incorrect(): assert isinstance(timerange.stopdt, datetime) assert timerange.startdt == datetime.fromtimestamp(1231006505, tz=timezone.utc) assert timerange.stopdt == datetime.fromtimestamp(1233360000, tz=timezone.utc) + assert timerange.timerange_str == '20090103-20090131' timerange = TimeRange.parse_timerange('1231006505000-1233360000000') assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange From e46a57bbd0d7ea7dbd52a5723edb5313753decfc Mon Sep 17 00:00:00 2001 From: robcaulk Date: Fri, 11 Nov 2022 18:05:32 +0100 Subject: [PATCH 07/41] move mem logs to debug --- freqtrade/freqai/data_kitchen.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 6b4586adc..7c4138f6f 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -1352,8 +1352,8 @@ class FreqaiDataKitchen: """ Ensure all values are float32 """ - start_mem = df.memory_usage().sum() / 1024**2 - print("Memory usage of dataframe is {:.2f} MB".format(start_mem)) + logger.debug(f"Memory usage of dataframe is " + f"{df.memory_usage().sum() / 1024**2:.2f} MB") df_dtypes = df.dtypes for column, dtype in df_dtypes.items(): @@ -1365,8 +1365,7 @@ class FreqaiDataKitchen: df_dtypes[column] = np.int32 df = df.astype(df_dtypes) - end_mem = df.memory_usage().sum() / 1024**2 - print("Memory usage after optimization is: {:.2f} MB".format(end_mem)) - print("Decreased by {:.1f}%".format(100 * (start_mem - end_mem) / start_mem)) + logger.debug(f"Memory usage after optimization is: " + f"{df.memory_usage().sum() / 1024**2:.2f} MB") return df From 214c6224755f6651938853c0b63581f160dd9060 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Sat, 12 Nov 2022 10:38:25 +0100 Subject: [PATCH 08/41] move dataframe converter to converter.py --- docs/configuration.md | 1 + docs/freqai-parameter-table.md | 2 +- freqtrade/constants.py | 1 + freqtrade/data/converter.py | 27 +++++++++++++++++++++++++++ freqtrade/freqai/data_kitchen.py | 27 +++------------------------ tests/freqai/test_freqai_interface.py | 2 +- 6 files changed, 34 insertions(+), 26 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index e773e1878..1fda27893 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -253,6 +253,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `add_config_files` | Additional config files. These files will be loaded and merged with the current config file. The files are resolved relative to the initial file.
*Defaults to `[]`*.
**Datatype:** List of strings | `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data.
*Defaults to `json`*.
**Datatype:** String | `dataformat_trades` | Data format to use to store historical trades data.
*Defaults to `jsongz`*.
**Datatype:** String +| `convert_df_to_32bit` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage and decreasing train/inference timing. (Currently only affects FreqAI use-cases)
**Datatype:** Boolean.
Default: `False`. ### Parameters in the strategy diff --git a/docs/freqai-parameter-table.md b/docs/freqai-parameter-table.md index 834960056..7ba54c1eb 100644 --- a/docs/freqai-parameter-table.md +++ b/docs/freqai-parameter-table.md @@ -18,7 +18,6 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | `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. | `follow_mode` | Use a `follower` that will look for models associated with a specific `identifier` and load those for inferencing. A `follower` will **not** train new models.
**Datatype:** Boolean.
Default: `False`. | `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`. -| `convert_df_to_float32` | Recast all numeric columns to float32, with the objective of reducing ram/disk usage and decreasing train/inference timing.
**Datatype:** Boolean.
Default: `False`. | `write_metrics_to_disk` | Collect train timings, inference timings and cpu usage in json file.
**Datatype:** Boolean.
Default: `False` | | **Feature parameters** | `feature_parameters` | A dictionary containing the parameters used to engineer the feature set. Details and examples are shown [here](freqai-feature-engineering.md).
**Datatype:** Dictionary. @@ -51,3 +50,4 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | | **Extraneous parameters** | `keras` | If the selected model makes use of Keras (typical for Tensorflow-based prediction models), this flag needs to be activated so that the model save/loading follows Keras standards.
**Datatype:** Boolean.
Default: `False`. | `conv_width` | The width of a convolutional neural network input tensor. This replaces the need for shifting candles (`include_shifted_candles`) by feeding in historical data points as the second dimension of the tensor. Technically, this parameter can also be used for regressors, but it only adds computational overhead and does not change the model training/prediction.
**Datatype:** Integer.
Default: `2`. +| `convert_df_to_32bit` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage and decreasing train/inference timing. This parameter is set in the main level of the Freqtrade configuration file (not inside FreqAI).
**Datatype:** Boolean.
Default: `False`. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 022cbd400..a9a38fa89 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -159,6 +159,7 @@ CONF_SCHEMA = { 'ignore_buying_expired_candle_after': {'type': 'number'}, 'trading_mode': {'type': 'string', 'enum': TRADING_MODES}, 'margin_mode': {'type': 'string', 'enum': MARGIN_MODES}, + 'convert_df_to_32bit': {'type': 'number', 'default': False}, 'liquidation_buffer': {'type': 'number', 'minimum': 0.0, 'maximum': 0.99}, 'backtest_breakdown': { 'type': 'array', diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py index 98ed15489..6a49a4799 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -7,6 +7,7 @@ from datetime import datetime, timezone from operator import itemgetter from typing import Dict, List +import numpy as np import pandas as pd from pandas import DataFrame, to_datetime @@ -313,3 +314,29 @@ def convert_ohlcv_format( if erase and convert_from != convert_to: logger.info(f"Deleting source data for {pair} / {timeframe}") src.ohlcv_purge(pair=pair, timeframe=timeframe, candle_type=candle_type) + + +def reduce_dataframe_footprint(df: DataFrame) -> DataFrame: + """ + Ensure all values are float32 in the incoming dataframe. + :param df: Dataframe to be converted to float/int 32s + :return: Dataframe converted to float/int 32s + """ + + logger.debug(f"Memory usage of dataframe is " + f"{df.memory_usage().sum() / 1024**2:.2f} MB") + + df_dtypes = df.dtypes + for column, dtype in df_dtypes.items(): + if column in ['open', 'high', 'low', 'close', 'volume']: + continue + if dtype == np.float64: + df_dtypes[column] = np.float32 + elif dtype == np.int64: + df_dtypes[column] = np.int32 + df = df.astype(df_dtypes) + + logger.debug(f"Memory usage after optimization is: " + f"{df.memory_usage().sum() / 1024**2:.2f} MB") + + return df diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index e6d50377b..5b2457eb8 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -19,6 +19,7 @@ from sklearn.neighbors import NearestNeighbors from freqtrade.configuration import TimeRange from freqtrade.constants import Config +from freqtrade.data.converter import reduce_dataframe_footprint from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_seconds from freqtrade.strategy.interface import IStrategy @@ -1275,8 +1276,8 @@ class FreqaiDataKitchen: dataframe = self.remove_special_chars_from_feature_names(dataframe) - if self.freqai_config.get('convert_df_to_float32', False): - dataframe = self.reduce_dataframe_footprint(dataframe) + if self.config.get('convert_df_to_32bit', False): + dataframe = reduce_dataframe_footprint(dataframe) return dataframe @@ -1492,25 +1493,3 @@ class FreqaiDataKitchen: dataframe.columns = dataframe.columns.str.replace(c, "") return dataframe - - def reduce_dataframe_footprint(self, df: DataFrame) -> DataFrame: - """ - Ensure all values are float32 - """ - logger.debug(f"Memory usage of dataframe is " - f"{df.memory_usage().sum() / 1024**2:.2f} MB") - - df_dtypes = df.dtypes - for column, dtype in df_dtypes.items(): - if column in ['open', 'high', 'low', 'close', 'volume']: - continue - if dtype == np.float64: - df_dtypes[column] = np.float32 - elif dtype == np.int64: - df_dtypes[column] = np.int32 - df = df.astype(df_dtypes) - - logger.debug(f"Memory usage after optimization is: " - f"{df.memory_usage().sum() / 1024**2:.2f} MB") - - return df diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index eab92c7f1..3a040181d 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -43,7 +43,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, freqai_conf.update({"strategy": "freqai_test_strat"}) freqai_conf['freqai']['feature_parameters'].update({"principal_component_analysis": pca}) freqai_conf['freqai']['feature_parameters'].update({"use_DBSCAN_to_remove_outliers": dbscan}) - freqai_conf['freqai'].update({"convert_df_to_float32": float32}) + freqai_conf.update({"convert_df_to_float32": float32}) strategy = get_patched_freqai_strategy(mocker, freqai_conf) exchange = get_patched_exchange(mocker, freqai_conf) From 942840da2d0c902fcbb8a9a43cf53dfc3f0feeb0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Nov 2022 15:22:44 +0100 Subject: [PATCH 09/41] Improve setting wording to keep future possibilities open --- config_examples/config_full.example.json | 1 + docs/configuration.md | 2 +- docs/freqai-parameter-table.md | 2 +- freqtrade/freqai/data_kitchen.py | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index 5a5096f81..b60957b58 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -204,6 +204,7 @@ "strategy_path": "user_data/strategies/", "recursive_strategy_search": false, "add_config_files": [], + "reduce_df_footprint": false, "dataformat_ohlcv": "json", "dataformat_trades": "jsongz" } diff --git a/docs/configuration.md b/docs/configuration.md index 1fda27893..d50b798f8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -253,7 +253,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `add_config_files` | Additional config files. These files will be loaded and merged with the current config file. The files are resolved relative to the initial file.
*Defaults to `[]`*.
**Datatype:** List of strings | `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data.
*Defaults to `json`*.
**Datatype:** String | `dataformat_trades` | Data format to use to store historical trades data.
*Defaults to `jsongz`*.
**Datatype:** String -| `convert_df_to_32bit` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage and decreasing train/inference timing. (Currently only affects FreqAI use-cases)
**Datatype:** Boolean.
Default: `False`. +| `reduce_df_footprint` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage and decreasing train/inference timing, as well as memory usage. (Currently only affects FreqAI use-cases)
**Datatype:** Boolean.
Default: `False`. ### Parameters in the strategy diff --git a/docs/freqai-parameter-table.md b/docs/freqai-parameter-table.md index 7ba54c1eb..c027a12b1 100644 --- a/docs/freqai-parameter-table.md +++ b/docs/freqai-parameter-table.md @@ -50,4 +50,4 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | | **Extraneous parameters** | `keras` | If the selected model makes use of Keras (typical for Tensorflow-based prediction models), this flag needs to be activated so that the model save/loading follows Keras standards.
**Datatype:** Boolean.
Default: `False`. | `conv_width` | The width of a convolutional neural network input tensor. This replaces the need for shifting candles (`include_shifted_candles`) by feeding in historical data points as the second dimension of the tensor. Technically, this parameter can also be used for regressors, but it only adds computational overhead and does not change the model training/prediction.
**Datatype:** Integer.
Default: `2`. -| `convert_df_to_32bit` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage and decreasing train/inference timing. This parameter is set in the main level of the Freqtrade configuration file (not inside FreqAI).
**Datatype:** Boolean.
Default: `False`. +| `reduce_df_footprint` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage and decreasing train/inference timing. This parameter is set in the main level of the Freqtrade configuration file (not inside FreqAI).
**Datatype:** Boolean.
Default: `False`. diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 5b2457eb8..d717858d2 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -1276,7 +1276,7 @@ class FreqaiDataKitchen: dataframe = self.remove_special_chars_from_feature_names(dataframe) - if self.config.get('convert_df_to_32bit', False): + if self.config.get('reduce_df_footprint', False): dataframe = reduce_dataframe_footprint(dataframe) return dataframe From a59d61472bc559b94afb1a7b256da7753e5ce3f2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Nov 2022 15:29:37 +0100 Subject: [PATCH 10/41] Add test for dataframe footprint reduction --- tests/data/test_converter.py | 37 +++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/tests/data/test_converter.py b/tests/data/test_converter.py index f74383d15..760ad8b76 100644 --- a/tests/data/test_converter.py +++ b/tests/data/test_converter.py @@ -3,18 +3,19 @@ import logging from pathlib import Path from shutil import copyfile +import numpy as np import pytest from freqtrade.configuration.timerange import TimeRange from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_format, ohlcv_fill_up_missing_data, ohlcv_to_dataframe, - trades_dict_to_list, trades_remove_duplicates, - trades_to_ohlcv, trim_dataframe) + reduce_dataframe_footprint, trades_dict_to_list, + trades_remove_duplicates, trades_to_ohlcv, trim_dataframe) from freqtrade.data.history import (get_timerange, load_data, load_pair_history, validate_backtest_data) from freqtrade.data.history.idatahandler import IDataHandler from freqtrade.enums import CandleType -from tests.conftest import log_has, log_has_re +from tests.conftest import generate_test_data, log_has, log_has_re from tests.data.test_history import _clean_test_file @@ -344,3 +345,33 @@ def test_convert_ohlcv_format(default_conf, testdatadir, tmpdir, file_base, cand assert file.exists() for file in (files_new): assert not file.exists() + + +def test_reduce_dataframe_footprint(): + data = generate_test_data('15m', 40) + + data['open_copy'] = data['open'] + data['close_copy'] = data['close'] + data['close_copy'] = data['close'] + + assert data['open'].dtype == np.float64 + assert data['open_copy'].dtype == np.float64 + assert data['close_copy'].dtype == np.float64 + + df2 = reduce_dataframe_footprint(data) + + # Does not modify original dataframe + assert data['open'].dtype == np.float64 + assert data['open_copy'].dtype == np.float64 + assert data['close_copy'].dtype == np.float64 + + # skips ohlcv columns + assert df2['open'].dtype == np.float64 + assert df2['high'].dtype == np.float64 + assert df2['low'].dtype == np.float64 + assert df2['close'].dtype == np.float64 + assert df2['volume'].dtype == np.float64 + + # Changes dtype of returned dataframe + assert df2['open_copy'].dtype == np.float32 + assert df2['close_copy'].dtype == np.float32 From 1e9e7887aadb2d39b1c8a1c336076b74337b10c7 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Sun, 13 Nov 2022 15:38:35 +0100 Subject: [PATCH 11/41] fix constants.py, fix freqai test --- docs/configuration.md | 2 +- freqtrade/constants.py | 2 +- tests/freqai/test_freqai_interface.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index d50b798f8..9dbfe7932 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -253,7 +253,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `add_config_files` | Additional config files. These files will be loaded and merged with the current config file. The files are resolved relative to the initial file.
*Defaults to `[]`*.
**Datatype:** List of strings | `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data.
*Defaults to `json`*.
**Datatype:** String | `dataformat_trades` | Data format to use to store historical trades data.
*Defaults to `jsongz`*.
**Datatype:** String -| `reduce_df_footprint` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage and decreasing train/inference timing, as well as memory usage. (Currently only affects FreqAI use-cases)
**Datatype:** Boolean.
Default: `False`. +| `reduce_df_footprint` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage (and decreasing train/inference timing in FreqAI). (Currently only affects FreqAI use-cases)
**Datatype:** Boolean.
Default: `False`. ### Parameters in the strategy diff --git a/freqtrade/constants.py b/freqtrade/constants.py index a9a38fa89..0c15883a4 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -159,7 +159,7 @@ CONF_SCHEMA = { 'ignore_buying_expired_candle_after': {'type': 'number'}, 'trading_mode': {'type': 'string', 'enum': TRADING_MODES}, 'margin_mode': {'type': 'string', 'enum': MARGIN_MODES}, - 'convert_df_to_32bit': {'type': 'number', 'default': False}, + 'reduce_df_footprint': {'type': 'number', 'default': False}, 'liquidation_buffer': {'type': 'number', 'minimum': 0.0, 'maximum': 0.99}, 'backtest_breakdown': { 'type': 'array', diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index 3a040181d..25bc99580 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -43,7 +43,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, freqai_conf.update({"strategy": "freqai_test_strat"}) 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({"convert_df_to_float32": float32}) + freqai_conf.update({"reduce_df_footprint": float32}) strategy = get_patched_freqai_strategy(mocker, freqai_conf) exchange = get_patched_exchange(mocker, freqai_conf) From 42b29cd307a4dbd1f2a477545dcbe353532710aa Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Nov 2022 19:31:49 +0100 Subject: [PATCH 12/41] Fix constants type --- freqtrade/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 0c15883a4..534d06fd4 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -159,7 +159,7 @@ CONF_SCHEMA = { 'ignore_buying_expired_candle_after': {'type': 'number'}, 'trading_mode': {'type': 'string', 'enum': TRADING_MODES}, 'margin_mode': {'type': 'string', 'enum': MARGIN_MODES}, - 'reduce_df_footprint': {'type': 'number', 'default': False}, + 'reduce_df_footprint': {'type': 'boolean', 'default': False}, 'liquidation_buffer': {'type': 'number', 'minimum': 0.0, 'maximum': 0.99}, 'backtest_breakdown': { 'type': 'array', From 3e6834e3f094b67775f3e9cd84d729cf2fe804a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 03:01:08 +0000 Subject: [PATCH 13/41] Bump tensorboard from 2.10.1 to 2.11.0 Bumps [tensorboard](https://github.com/tensorflow/tensorboard) from 2.10.1 to 2.11.0. - [Release notes](https://github.com/tensorflow/tensorboard/releases) - [Changelog](https://github.com/tensorflow/tensorboard/blob/master/RELEASE.md) - [Commits](https://github.com/tensorflow/tensorboard/compare/2.10.1...2.11.0) --- updated-dependencies: - dependency-name: tensorboard dependency-type: direct:production update-type: version-update:semver-minor ... 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 a90b9df69..66730e29f 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -8,4 +8,4 @@ joblib==1.2.0 catboost==1.1.1; platform_machine != 'aarch64' lightgbm==3.3.3 xgboost==1.7.1 -tensorboard==2.10.1 +tensorboard==2.11.0 From 001602e0340d2778f2ae82f3c33edb9b2c140729 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 03:01:11 +0000 Subject: [PATCH 14/41] Bump nbconvert from 7.2.3 to 7.2.4 Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 7.2.3 to 7.2.4. - [Release notes](https://github.com/jupyter/nbconvert/releases) - [Changelog](https://github.com/jupyter/nbconvert/blob/main/CHANGELOG.md) - [Commits](https://github.com/jupyter/nbconvert/compare/v7.2.3...v7.2.4) --- updated-dependencies: - dependency-name: nbconvert 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 a46ec7e48..bbd508760 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -20,7 +20,7 @@ isort==5.10.1 time-machine==2.8.2 # Convert jupyter notebooks to markdown documents -nbconvert==7.2.3 +nbconvert==7.2.4 # mypy types types-cachetools==5.2.1 From 9d8d18d76b1bf36902512aa1491764baa45f92aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 03:01:42 +0000 Subject: [PATCH 15/41] Bump pytest-asyncio from 0.20.1 to 0.20.2 Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.20.1 to 0.20.2. - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Changelog](https://github.com/pytest-dev/pytest-asyncio/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.20.1...v0.20.2) --- updated-dependencies: - dependency-name: pytest-asyncio 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 a46ec7e48..c26d9b3db 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,7 +11,7 @@ flake8-tidy-imports==4.8.0 mypy==0.982 pre-commit==2.20.0 pytest==7.2.0 -pytest-asyncio==0.20.1 +pytest-asyncio==0.20.2 pytest-cov==4.0.0 pytest-mock==3.10.0 pytest-random-order==1.0.4 From ce269b7984cebdce6d2a33497569814ba36138e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 03:02:14 +0000 Subject: [PATCH 16/41] Bump fastapi from 0.85.1 to 0.87.0 Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.85.1 to 0.87.0. - [Release notes](https://github.com/tiangolo/fastapi/releases) - [Commits](https://github.com/tiangolo/fastapi/compare/0.85.1...0.87.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 b98973cc7..006914c52 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,7 +35,7 @@ orjson==3.8.1 sdnotify==0.3.2 # API Server -fastapi==0.85.1 +fastapi==0.87.0 pydantic==1.10.2 uvicorn==0.19.0 pyjwt==2.6.0 From 48c4d8d2df5a9ca3bee321709d108410ffd3446c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 03:02:21 +0000 Subject: [PATCH 17/41] Bump types-requests from 2.28.11.2 to 2.28.11.4 Bumps [types-requests](https://github.com/python/typeshed) from 2.28.11.2 to 2.28.11.4. - [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 a46ec7e48..32aa598a5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -25,6 +25,6 @@ nbconvert==7.2.3 # mypy types types-cachetools==5.2.1 types-filelock==3.2.7 -types-requests==2.28.11.2 +types-requests==2.28.11.4 types-tabulate==0.9.0.0 types-python-dateutil==2.8.19.2 From bbfcaca9e00e2afa97724846bea3b024c6d901f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 03:02:26 +0000 Subject: [PATCH 18/41] Bump pymdown-extensions from 9.7 to 9.8 Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 9.7 to 9.8. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/9.7...9.8) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-type: direct:production update-type: version-update:semver-minor ... 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 d7d2e27b7..8a365feed 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -2,5 +2,5 @@ markdown==3.3.7 mkdocs==1.4.2 mkdocs-material==8.5.8 mdx_truly_sane_lists==1.3 -pymdown-extensions==9.7 +pymdown-extensions==9.8 jinja2==3.1.2 From 9843fb20876cb75aef5bb05f90eeaac0e48a0337 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 05:55:09 +0000 Subject: [PATCH 19/41] Bump mkdocs-material from 8.5.8 to 8.5.10 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.5.8 to 8.5.10. - [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/8.5.8...8.5.10) --- 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 8a365feed..224e9b548 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==8.5.8 +mkdocs-material==8.5.10 mdx_truly_sane_lists==1.3 pymdown-extensions==9.8 jinja2==3.1.2 From b2de0704622e1d4ec413c5a3050c4999e4fd6524 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 14 Nov 2022 07:01:49 +0100 Subject: [PATCH 20/41] bump types-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 ca42402dd..532ddd236 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: additional_dependencies: - types-cachetools==5.2.1 - types-filelock==3.2.7 - - types-requests==2.28.11.2 + - types-requests==2.28.11.4 - types-tabulate==0.9.0.0 - types-python-dateutil==2.8.19.2 # stages: [push] From 60de797dcc7a292f0a87e41c3526f034364729c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 06:03:58 +0000 Subject: [PATCH 21/41] Bump psutil from 5.9.3 to 5.9.4 Bumps [psutil](https://github.com/giampaolo/psutil) from 5.9.3 to 5.9.4. - [Release notes](https://github.com/giampaolo/psutil/releases) - [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst) - [Commits](https://github.com/giampaolo/psutil/compare/release-5.9.3...release-5.9.4) --- updated-dependencies: - dependency-name: psutil 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 b98973cc7..b06968cf3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,7 +40,7 @@ pydantic==1.10.2 uvicorn==0.19.0 pyjwt==2.6.0 aiofiles==22.1.0 -psutil==5.9.3 +psutil==5.9.4 # Support for colorized terminal output colorama==0.4.6 From 7275d485164cd32c8097fa6088c1bb6ece2d32c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 06:04:37 +0000 Subject: [PATCH 22/41] Bump ccxt from 2.1.54 to 2.1.75 Bumps [ccxt](https://github.com/ccxt/ccxt) from 2.1.54 to 2.1.75. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/2.1.54...2.1.75) --- 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 b98973cc7..f366eb35c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.23.4 pandas==1.5.1 pandas-ta==0.3.14b -ccxt==2.1.54 +ccxt==2.1.75 # Pin cryptography for now due to rust build errors with piwheels cryptography==38.0.1 aiohttp==3.8.3 From cf5cda4df56bccb656577de6524032bac623513e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 06:04:54 +0000 Subject: [PATCH 23/41] Bump sqlalchemy from 1.4.43 to 1.4.44 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.4.43 to 1.4.44. - [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-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 b98973cc7..eb47a644b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ ccxt==2.1.54 # Pin cryptography for now due to rust build errors with piwheels cryptography==38.0.1 aiohttp==3.8.3 -SQLAlchemy==1.4.43 +SQLAlchemy==1.4.44 python-telegram-bot==13.14 arrow==1.2.3 cachetools==4.2.2 From 60449d9bec2bd46ef15ecac2dbd48ba221dcfd33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 07:32:31 +0000 Subject: [PATCH 24/41] Bump cryptography from 38.0.1 to 38.0.3 Bumps [cryptography](https://github.com/pyca/cryptography) from 38.0.1 to 38.0.3. - [Release notes](https://github.com/pyca/cryptography/releases) - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/38.0.1...38.0.3) --- updated-dependencies: - dependency-name: cryptography 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 ed87c20fe..5e2fc5f2c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ pandas-ta==0.3.14b ccxt==2.1.75 # Pin cryptography for now due to rust build errors with piwheels -cryptography==38.0.1 +cryptography==38.0.3 aiohttp==3.8.3 SQLAlchemy==1.4.43 python-telegram-bot==13.14 From 721998521b0dee300af77f4853e3298a4e62747e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 07:33:03 +0000 Subject: [PATCH 25/41] Bump types-python-dateutil from 2.8.19.2 to 2.8.19.3 Bumps [types-python-dateutil](https://github.com/python/typeshed) from 2.8.19.2 to 2.8.19.3. - [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 cde38e095..8db209643 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -27,4 +27,4 @@ types-cachetools==5.2.1 types-filelock==3.2.7 types-requests==2.28.11.4 types-tabulate==0.9.0.0 -types-python-dateutil==2.8.19.2 +types-python-dateutil==2.8.19.3 From 4cece8720a6580f03a1c53a598d713e9c5d0ea58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 07:33:06 +0000 Subject: [PATCH 26/41] Bump mypy from 0.982 to 0.990 Bumps [mypy](https://github.com/python/mypy) from 0.982 to 0.990. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.982...v0.990) --- updated-dependencies: - dependency-name: mypy 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 cde38e095..f4575e176 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,7 +8,7 @@ coveralls==3.3.1 flake8==5.0.4 flake8-tidy-imports==4.8.0 -mypy==0.982 +mypy==0.990 pre-commit==2.20.0 pytest==7.2.0 pytest-asyncio==0.20.2 From c12dcd9b9bbe4d2cc5e161db1b3b070d55db2317 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 14 Nov 2022 08:40:09 +0100 Subject: [PATCH 27/41] update 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 532ddd236..b4a8336b9 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.4 - types-tabulate==0.9.0.0 - - types-python-dateutil==2.8.19.2 + - types-python-dateutil==2.8.19.3 # stages: [push] - repo: https://github.com/pycqa/isort From c72ffad69833b8774746337423f18571218bbd59 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 14 Nov 2022 13:08:29 +0000 Subject: [PATCH 28/41] Add starlette test dependency --- requirements-dev.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index a46ec7e48..7d96c3672 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,6 +18,8 @@ pytest-random-order==1.0.4 isort==5.10.1 # For datetime mocking time-machine==2.8.2 +# fastapi testing +httpx==0.23.0 # Convert jupyter notebooks to markdown documents nbconvert==7.2.3 From 30b467906c0c8b73f6e31002853a4b50bffa7b15 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 14 Nov 2022 19:40:57 +0100 Subject: [PATCH 29/41] Delist FTX, following ccxt's delisting. --- .gitignore | 1 - config_examples/config_ftx.example.json | 96 ------- docs/configuration.md | 2 +- docs/exchanges.md | 24 -- docs/includes/pairlists.md | 2 +- docs/stoploss.md | 2 +- docs/strategy-customization.md | 2 +- docs/utils.md | 2 - freqtrade/commands/build_config_commands.py | 1 - freqtrade/exchange/__init__.py | 1 - freqtrade/exchange/common.py | 1 - freqtrade/exchange/ftx.py | 178 ------------- freqtrade/leverage/interest.py | 4 - tests/commands/test_build_config.py | 2 +- tests/conftest.py | 23 +- tests/data/test_datahandler.py | 2 +- tests/exchange/test_ccxt_compat.py | 10 - tests/exchange/test_exchange.py | 42 +-- tests/exchange/test_ftx.py | 272 -------------------- tests/leverage/test_interest.py | 5 - tests/plugins/test_pairlist.py | 8 +- tests/test_freqtradebot.py | 2 +- 22 files changed, 15 insertions(+), 667 deletions(-) delete mode 100644 config_examples/config_ftx.example.json delete mode 100644 freqtrade/exchange/ftx.py delete mode 100644 tests/exchange/test_ftx.py diff --git a/.gitignore b/.gitignore index e400c01f5..758a324f4 100644 --- a/.gitignore +++ b/.gitignore @@ -109,7 +109,6 @@ target/ !*.gitkeep !config_examples/config_binance.example.json !config_examples/config_bittrex.example.json -!config_examples/config_ftx.example.json !config_examples/config_full.example.json !config_examples/config_kraken.example.json !config_examples/config_freqai.example.json diff --git a/config_examples/config_ftx.example.json b/config_examples/config_ftx.example.json deleted file mode 100644 index c49898277..000000000 --- a/config_examples/config_ftx.example.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "max_open_trades": 3, - "stake_currency": "USD", - "stake_amount": 50, - "tradable_balance_ratio": 0.99, - "fiat_display_currency": "USD", - "timeframe": "5m", - "dry_run": true, - "cancel_open_orders_on_exit": false, - "unfilledtimeout": { - "entry": 10, - "exit": 10, - "exit_timeout_count": 0, - "unit": "minutes" - }, - "entry_pricing": { - "price_side": "same", - "use_order_book": true, - "order_book_top": 1, - "price_last_balance": 0.0, - "check_depth_of_market": { - "enabled": false, - "bids_to_ask_delta": 1 - } - }, - "exit_pricing": { - "price_side": "same", - "use_order_book": true, - "order_book_top": 1 - }, - "exchange": { - "name": "ftx", - "key": "your_exchange_key", - "secret": "your_exchange_secret", - "ccxt_config": {}, - "ccxt_async_config": {}, - "pair_whitelist": [ - "BTC/USD", - "ETH/USD", - "BNB/USD", - "USDT/USD", - "LTC/USD", - "SRM/USD", - "SXP/USD", - "XRP/USD", - "DOGE/USD", - "1INCH/USD", - "CHZ/USD", - "MATIC/USD", - "LINK/USD", - "OXY/USD", - "SUSHI/USD" - ], - "pair_blacklist": [ - "FTT/USD" - ] - }, - "pairlists": [ - {"method": "StaticPairList"} - ], - "edge": { - "enabled": false, - "process_throttle_secs": 3600, - "calculate_since_number_of_days": 7, - "allowed_risk": 0.01, - "stoploss_range_min": -0.01, - "stoploss_range_max": -0.1, - "stoploss_range_step": -0.01, - "minimum_winrate": 0.60, - "minimum_expectancy": 0.20, - "min_trade_number": 10, - "max_trade_duration_minute": 1440, - "remove_pumps": false - }, - "telegram": { - "enabled": false, - "token": "your_telegram_token", - "chat_id": "your_telegram_chat_id" - }, - "api_server": { - "enabled": false, - "listen_ip_address": "127.0.0.1", - "listen_port": 8080, - "verbosity": "error", - "jwt_secret_key": "somethingrandom", - "CORS_origins": [], - "username": "freqtrader", - "password": "SuperSecurePassword" - }, - "bot_name": "freqtrade", - "initial_state": "running", - "force_entry_enable": false, - "internals": { - "process_throttle_secs": 5 - } -} diff --git a/docs/configuration.md b/docs/configuration.md index 9dbfe7932..ce4453561 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -553,7 +553,7 @@ The possible values are: `GTC` (default), `FOK` or `IOC`. ``` !!! Warning - This is ongoing work. For now, it is supported only for binance, gate, ftx and kucoin. + This is ongoing work. For now, it is supported only for binance, gate and kucoin. Please don't change the default value unless you know what you are doing and have researched the impact of using different values for your particular exchange. ### What values can be used for fiat_display_currency? diff --git a/docs/exchanges.md b/docs/exchanges.md index bae7c929c..b4eb7e023 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -173,30 +173,6 @@ res = [p for p, x in lm.items() if 'US' in x['info']['prohibitedIn']] print(res) ``` -## FTX - -!!! Warning - Due to the current situation, we can no longer recommend FTX. - Please make sure to investigate the current situation before depositing any funds to FTX. - -!!! Tip "Stoploss on Exchange" - FTX supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it. - You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type of stoploss shall be used. - -### Using subaccounts - -To use subaccounts with FTX, you need to edit the configuration and add the following: - -``` json -"exchange": { - "ccxt_config": { - "headers": { - "FTX-SUBACCOUNT": "name" - } - }, -} -``` - ## Kucoin Kucoin requires a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows: diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 8ae38b0d4..d61718c7d 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -268,7 +268,7 @@ This option is disabled by default, and will only apply if set to > 0. The `max_value` setting removes pairs where the minimum value change is above a specified value. This is useful when an exchange has unbalanced limits. For example, if step-size = 1 (so you can only buy 1, or 2, or 3, but not 1.1 Coins) - and the price is pretty high (like 20\$) as the coin has risen sharply since the last limit adaption. As a result of the above, you can only buy for 20\$, or 40\$ - but not for 25\$. -On exchanges that deduct fees from the receiving currency (e.g. binance, FTX) - this can result in high value coins / amounts that are unsellable as the amount is slightly below the limit. +On exchanges that deduct fees from the receiving currency (e.g. binance) - this can result in high value coins / amounts that are unsellable as the amount is slightly below the limit. The `low_price_ratio` setting removes pairs where a raise of 1 price unit (pip) is above the `low_price_ratio` ratio. This option is disabled by default, and will only apply if set to > 0. diff --git a/docs/stoploss.md b/docs/stoploss.md index a8285cf04..20e53d8f5 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -24,7 +24,7 @@ These modes can be configured with these values: ``` !!! Note - Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), FTX (stop limit and stop-market) Gateio (stop-limit), and Kucoin (stop-limit and stop-market) as of now. + Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), Gateio (stop-limit), and Kucoin (stop-limit and stop-market) as of now. Do not set too low/tight stoploss value if using stop loss on exchange! If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work. diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index f036182e3..3e8bab8ee 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -723,7 +723,7 @@ if self.dp.runmode.value in ('live', 'dry_run'): !!! Warning Although the ticker data structure is a part of the ccxt Unified Interface, the values returned by this method can - vary for different exchanges. For instance, many exchanges do not return `vwap` values, the FTX exchange + vary for different exchanges. For instance, many exchanges do not return `vwap` values, some exchanges does not always fills in the `last` field (so it can be None), etc. So you need to carefully verify the ticker data returned from the exchange and add appropriate error handling / defaults. diff --git a/docs/utils.md b/docs/utils.md index ee8793159..3d8a3bd03 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -263,7 +263,6 @@ equos True missing opt: fetchTicker, fetchTickers eterbase True fcoin True missing opt: fetchMyTrades, fetchTickers fcoinjp True missing opt: fetchMyTrades, fetchTickers -ftx True gateio True gemini True gopax True @@ -369,7 +368,6 @@ fcoin True missing opt: fetchMyTrades, fetchTickers fcoinjp True missing opt: fetchMyTrades, fetchTickers flowbtc False missing: fetchOrder, fetchOHLCV foxbit False missing: fetchOrder, fetchOHLCV -ftx True gateio True gemini True gopax True diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 1abd26328..f95a08ba5 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -108,7 +108,6 @@ def ask_user_config() -> Dict[str, Any]: "binance", "binanceus", "bittrex", - "ftx", "gateio", "huobi", "kraken", diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 8294838b1..9aed5c507 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -18,7 +18,6 @@ from freqtrade.exchange.exchange_utils import (amount_to_contract_precision, amo timeframe_to_next_date, timeframe_to_prev_date, timeframe_to_seconds, validate_exchange, validate_exchanges) -from freqtrade.exchange.ftx import Ftx from freqtrade.exchange.gateio import Gateio from freqtrade.exchange.hitbtc import Hitbtc from freqtrade.exchange.huobi import Huobi diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index 5765dc459..6d09c4f95 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -52,7 +52,6 @@ MAP_EXCHANGE_CHILDCLASS = { SUPPORTED_EXCHANGES = [ 'binance', 'bittrex', - 'ftx', 'gateio', 'huobi', 'kraken', diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py deleted file mode 100644 index 6a43ab302..000000000 --- a/freqtrade/exchange/ftx.py +++ /dev/null @@ -1,178 +0,0 @@ -""" FTX exchange subclass """ -import logging -from typing import Any, Dict, List, Optional, Tuple - -import ccxt - -from freqtrade.constants import BuySell -from freqtrade.enums import MarginMode, TradingMode -from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, - OperationalException, TemporaryError) -from freqtrade.exchange import Exchange -from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT, retrier -from freqtrade.misc import safe_value_fallback2 - - -logger = logging.getLogger(__name__) - - -class Ftx(Exchange): - - _ft_has: Dict = { - "order_time_in_force": ['GTC', 'IOC', 'PO'], - "stoploss_on_exchange": True, - "ohlcv_candle_limit": 1500, - "ohlcv_require_since": True, - "ohlcv_volume_currency": "quote", - "mark_ohlcv_price": "index", - "mark_ohlcv_timeframe": "1h", - } - - _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ - # TradingMode.SPOT always supported and not required in this list - # (TradingMode.MARGIN, MarginMode.CROSS), - # (TradingMode.FUTURES, MarginMode.CROSS) - ] - - def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: - """ - Verify stop_loss against stoploss-order value (limit or price) - Returns True if adjustment is necessary. - """ - return order['type'] == 'stop' and ( - side == "sell" and stop_loss > float(order['price']) or - side == "buy" and stop_loss < float(order['price']) - ) - - @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, - order_types: Dict, side: BuySell, leverage: float) -> Dict: - """ - Creates a stoploss order. - depending on order_types.stoploss configuration, uses 'market' or limit order. - - Limit orders are defined by having orderPrice set, otherwise a market order is used. - """ - limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) - if side == "sell": - limit_rate = stop_price * limit_price_pct - else: - limit_rate = stop_price * (2 - limit_price_pct) - - ordertype = "stop" - - stop_price = self.price_to_precision(pair, stop_price) - - if self._config['dry_run']: - dry_order = self.create_dry_run_order( - pair, ordertype, side, amount, stop_price, leverage, stop_loss=True) - return dry_order - - try: - params = self._params.copy() - if order_types.get('stoploss', 'market') == 'limit': - # set orderPrice to place limit order, otherwise it's a market order - params['orderPrice'] = limit_rate - if self.trading_mode == TradingMode.FUTURES: - params.update({'reduceOnly': True}) - - params['stopPrice'] = stop_price - amount = self.amount_to_precision(pair, amount) - - self._lev_prep(pair, leverage, side) - order = self._api.create_order(symbol=pair, type=ordertype, side=side, - amount=amount, params=params) - self._log_exchange_response('create_stoploss_order', order) - logger.info('stoploss order added for %s. ' - 'stop price: %s.', pair, stop_price) - return order - except ccxt.InsufficientFunds as e: - raise InsufficientFundsError( - f'Insufficient funds to create {ordertype} {side} order on market {pair}. ' - f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' - f'Message: {e}') from e - except ccxt.InvalidOrder as e: - raise InvalidOrderException( - f'Could not create {ordertype} {side} order on market {pair}. ' - f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' - f'Message: {e}') from e - except ccxt.DDoSProtection as e: - raise DDosProtection(e) from e - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}') from e - except ccxt.BaseError as e: - raise OperationalException(e) from e - - @retrier(retries=API_FETCH_ORDER_RETRY_COUNT) - def fetch_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict: - if self._config['dry_run']: - return self.fetch_dry_run_order(order_id) - - try: - orders = self._api.fetch_orders(pair, None, params={'type': 'stop'}) - - order = [order for order in orders if order['id'] == order_id] - self._log_exchange_response('fetch_stoploss_order', order) - if len(order) == 1: - if order[0].get('status') == 'closed': - # Trigger order was triggered ... - real_order_id: Optional[str] = order[0].get('info', {}).get('orderId') - # OrderId may be None for stoploss-market orders - # So we need to get it through the endpoint - # /conditional_orders/{conditional_order_id}/triggers - if not real_order_id: - res = self._api.privateGetConditionalOrdersConditionalOrderIdTriggers( - params={'conditional_order_id': order_id}) - self._log_exchange_response('fetch_stoploss_order2', res) - real_order_id = res['result'][0]['orderId'] if res.get( - 'result', []) else None - - if real_order_id: - order1 = self._api.fetch_order(real_order_id, pair) - self._log_exchange_response('fetch_stoploss_order1', order1) - # Fake type to stop - as this was really a stop order. - order1['id_stop'] = order1['id'] - order1['id'] = order_id - order1['type'] = 'stop' - order1['status_stop'] = 'triggered' - return order1 - - return order[0] - else: - raise InvalidOrderException(f"Could not get stoploss order for id {order_id}") - - except ccxt.InvalidOrder as e: - raise InvalidOrderException( - f'Tried to get an invalid order (id: {order_id}). Message: {e}') from e - except ccxt.DDoSProtection as e: - raise DDosProtection(e) from e - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not get order due to {e.__class__.__name__}. Message: {e}') from e - except ccxt.BaseError as e: - raise OperationalException(e) from e - - @retrier - def cancel_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict: - if self._config['dry_run']: - return {} - try: - order = self._api.cancel_order(order_id, pair, params={'type': 'stop'}) - self._log_exchange_response('cancel_stoploss_order', order) - return order - except ccxt.InvalidOrder as e: - raise InvalidOrderException( - f'Could not cancel order. Message: {e}') from e - except ccxt.DDoSProtection as e: - raise DDosProtection(e) from e - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not cancel order due to {e.__class__.__name__}. Message: {e}') from e - except ccxt.BaseError as e: - raise OperationalException(e) from e - - def get_order_id_conditional(self, order: Dict[str, Any]) -> str: - if order['type'] == 'stop': - return safe_value_fallback2(order, order, 'id_stop', 'id') - return order['id'] diff --git a/freqtrade/leverage/interest.py b/freqtrade/leverage/interest.py index ddeea2b42..d18cc458f 100644 --- a/freqtrade/leverage/interest.py +++ b/freqtrade/leverage/interest.py @@ -35,9 +35,5 @@ def interest( elif exchange_name == "kraken": # Rounded based on https://kraken-fees-calculator.github.io/ return borrowed * rate * (one + FtPrecise(ceil(hours / four))) - elif exchange_name == "ftx": - # As Explained under #Interest rates section in - # https://help.ftx.com/hc/en-us/articles/360053007671-Spot-Margin-Trading-Explainer - return borrowed * rate * FtPrecise(ceil(hours)) / twenty_four else: raise OperationalException(f"Leverage not available on {exchange_name} with freqtrade") diff --git a/tests/commands/test_build_config.py b/tests/commands/test_build_config.py index e30d5bf94..7bf374ae0 100644 --- a/tests/commands/test_build_config.py +++ b/tests/commands/test_build_config.py @@ -30,7 +30,7 @@ def test_validate_is_int(): assert not validate_is_int('-ee') -@pytest.mark.parametrize('exchange', ['bittrex', 'binance', 'kraken', 'ftx']) +@pytest.mark.parametrize('exchange', ['bittrex', 'binance', 'kraken']) def test_start_new_config(mocker, caplog, exchange): wt_mock = mocker.patch.object(Path, "write_text", MagicMock()) mocker.patch.object(Path, "exists", MagicMock(return_value=True)) diff --git a/tests/conftest.py b/tests/conftest.py index 9f71709f1..d228c64b7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1748,28 +1748,7 @@ def limit_buy_order_canceled_empty(request): # https://docs.pytest.org/en/latest/example/parametrize.html#apply-indirect-on-particular-arguments exchange_name = request.param - if exchange_name == 'ftx': - return { - 'info': {}, - 'id': '1234512345', - 'clientOrderId': None, - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, - 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), - 'lastTradeTimestamp': None, - 'symbol': 'LTC/USDT', - 'type': 'limit', - 'side': 'buy', - 'price': 34.3225, - 'amount': 0.55, - 'cost': 0.0, - 'average': None, - 'filled': 0.0, - 'remaining': 0.0, - 'status': 'closed', - 'fee': None, - 'trades': None - } - elif exchange_name == 'kraken': + if exchange_name == 'kraken': return { 'info': {}, 'id': 'AZNPFF-4AC4N-7MKTAT', diff --git a/tests/data/test_datahandler.py b/tests/data/test_datahandler.py index c067d0339..4d6489f11 100644 --- a/tests/data/test_datahandler.py +++ b/tests/data/test_datahandler.py @@ -70,7 +70,7 @@ def test_datahandler_ohlcv_regex(filename, pair, timeframe, candletype): ('BTC_USDT_USDT', 'BTC/USDT:USDT'), # Futures ('XRP_USDT_USDT', 'XRP/USDT:USDT'), # futures ('BTC-PERP', 'BTC-PERP'), - ('BTC-PERP_USDT', 'BTC-PERP:USDT'), # potential FTX case + ('BTC-PERP_USDT', 'BTC-PERP:USDT'), ('UNITTEST_USDT', 'UNITTEST/USDT'), ]) def test_rebuild_pair_from_filename(input, expected): diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index c06cfbe14..55d463c68 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -45,16 +45,6 @@ EXCHANGES = { 'leverage_tiers_public': False, 'leverage_in_spot_market': True, }, - # 'ftx': { - # 'pair': 'BTC/USD', - # 'stake_currency': 'USD', - # 'hasQuoteVolume': True, - # 'timeframe': '5m', - # 'futures_pair': 'BTC/USD:USD', - # 'futures': False, - # 'leverage_tiers_public': False, # TODO: Set to True once implemented on CCXT - # 'leverage_in_spot_market': True, - # }, 'kucoin': { 'pair': 'XRP/USDT', 'stake_currency': 'USDT', diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 25ba294a3..a719496e5 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', 'ftx', 'gateio'] +EXCHANGES = ['bittrex', 'binance', 'kraken', 'gateio'] get_entry_rate_data = [ ('other', 20, 19, 10, 0.0, 20), # Full ask side @@ -3162,19 +3162,16 @@ 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('freqtrade.exchange.Exchange.fetch_stoploss_order', return_value={'for': 123}) - mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order', return_value={'for': 123}) mocker.patch('freqtrade.exchange.Gateio.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('freqtrade.exchange.Ftx.cancel_stoploss_order', return_value=res) mocker.patch('freqtrade.exchange.Gateio.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('freqtrade.exchange.Ftx.cancel_stoploss_order', return_value='canceled') mocker.patch('freqtrade.exchange.Gateio.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) @@ -3182,7 +3179,6 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name): exc = InvalidOrderException("") mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', side_effect=exc) - mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order', side_effect=exc) mocker.patch('freqtrade.exchange.Gateio.fetch_stoploss_order', side_effect=exc) co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co['amount'] == 555 @@ -3191,7 +3187,6 @@ 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('freqtrade.exchange.Ftx.cancel_stoploss_order', side_effect=exc) mocker.patch('freqtrade.exchange.Gateio.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) @@ -3253,9 +3248,6 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog): @pytest.mark.usefixtures("init_persistence") @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_fetch_stoploss_order(default_conf, mocker, exchange_name): - # Don't test FTX here - that needs a separate test - if exchange_name == 'ftx': - return default_conf['dry_run'] = True order = MagicMock() order.myid = 123 @@ -3699,16 +3691,6 @@ def test_date_minus_candles(): # no darkpools ("BTC/EUR.d", 'BTC', 'EUR', "kraken", True, False, False, 'spot', {"darkpool": True}, False), - ("BTC/USD", 'BTC', 'USD', "ftx", True, False, False, 'spot', {}, True), - ("USD/BTC", 'USD', 'BTC', "ftx", True, False, False, 'spot', {}, True), - # Can only trade spot markets - ("BTC/USD", 'BTC', 'USD', "ftx", False, False, True, 'spot', {}, False), - ("BTC/USD", 'BTC', 'USD', "ftx", False, False, True, 'futures', {}, True), - # Can only trade spot markets - ("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'spot', {}, False), - ("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'margin', {}, False), - ("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'futures', {}, True), - ("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'spot', {}, False), ("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'margin', {}, False), ("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'futures', {}, True), @@ -3841,7 +3823,7 @@ def test_calculate_backoff(retrycount, max_retries, expected): assert calculate_backoff(retrycount, max_retries) == expected -@pytest.mark.parametrize("exchange_name", ['binance', 'ftx']) +@pytest.mark.parametrize("exchange_name", ['binance']) def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name): api_mock = MagicMock() api_mock.fetch_funding_history = MagicMock(return_value=[ @@ -3909,7 +3891,7 @@ def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name): ) -@pytest.mark.parametrize('exchange', ['binance', 'kraken', 'ftx']) +@pytest.mark.parametrize('exchange', ['binance', 'kraken']) @pytest.mark.parametrize('stake_amount,leverage,min_stake_with_lev', [ (9.0, 3.0, 3.0), (20.0, 5.0, 4.0), @@ -3930,8 +3912,6 @@ def test_get_stake_amount_considering_leverage( @pytest.mark.parametrize("exchange_name,trading_mode", [ ("binance", TradingMode.FUTURES), - ("ftx", TradingMode.MARGIN), - ("ftx", TradingMode.FUTURES) ]) def test__set_leverage(mocker, default_conf, exchange_name, trading_mode): @@ -3982,9 +3962,6 @@ def test_set_margin_mode(mocker, default_conf, margin_mode): ("kraken", TradingMode.SPOT, None, False), ("kraken", TradingMode.MARGIN, MarginMode.ISOLATED, True), ("kraken", TradingMode.FUTURES, MarginMode.ISOLATED, True), - ("ftx", TradingMode.SPOT, None, False), - ("ftx", TradingMode.MARGIN, MarginMode.ISOLATED, True), - ("ftx", TradingMode.FUTURES, MarginMode.ISOLATED, True), ("bittrex", TradingMode.SPOT, None, False), ("bittrex", TradingMode.MARGIN, MarginMode.CROSS, True), ("bittrex", TradingMode.MARGIN, MarginMode.ISOLATED, True), @@ -4005,8 +3982,6 @@ def test_set_margin_mode(mocker, default_conf, margin_mode): ("binance", TradingMode.FUTURES, MarginMode.CROSS, True), ("kraken", TradingMode.MARGIN, MarginMode.CROSS, True), ("kraken", TradingMode.FUTURES, MarginMode.CROSS, True), - ("ftx", TradingMode.MARGIN, MarginMode.CROSS, True), - ("ftx", TradingMode.FUTURES, MarginMode.CROSS, True), ("gateio", TradingMode.MARGIN, MarginMode.CROSS, True), ("gateio", TradingMode.FUTURES, MarginMode.CROSS, True), @@ -4015,8 +3990,6 @@ def test_set_margin_mode(mocker, default_conf, margin_mode): # ("binance", TradingMode.FUTURES, MarginMode.CROSS, False), # ("kraken", TradingMode.MARGIN, MarginMode.CROSS, False), # ("kraken", TradingMode.FUTURES, MarginMode.CROSS, False), - # ("ftx", TradingMode.MARGIN, MarginMode.CROSS, False), - # ("ftx", TradingMode.FUTURES, MarginMode.CROSS, False), # ("gateio", TradingMode.MARGIN, MarginMode.CROSS, False), # ("gateio", TradingMode.FUTURES, MarginMode.CROSS, False), ]) @@ -4046,7 +4019,6 @@ def test_validate_trading_mode_and_margin_mode( ("bibox", "futures", {"has": {"fetchCurrencies": False}, "options": {"defaultType": "swap"}}), ("bybit", "spot", {"options": {"defaultType": "spot"}}), ("bybit", "futures", {"options": {"defaultType": "linear"}}), - ("ftx", "futures", {"options": {"defaultType": "swap"}}), ("gateio", "futures", {"options": {"defaultType": "swap"}}), ("hitbtc", "futures", {"options": {"defaultType": "swap"}}), ("kraken", "futures", {"options": {"defaultType": "swap"}}), @@ -4223,11 +4195,6 @@ def test_combine_funding_and_mark( # ('kraken', "2021-09-01 00:00:00", "2021-09-01 07:59:59", 30.0, -0.0012443999999999999), # ('kraken', "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, 0.0045759), # ('kraken', "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0008289), - ('ftx', 0, 2, "2021-09-01 00:10:00", "2021-09-01 00:30:00", 30.0, 0.0), - ('ftx', 0, 9, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, 0.0010008), - ('ftx', 0, 13, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, 0.0146691), - ('ftx', 0, 9, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 50.0, 0.001668), - ('ftx', 1, 9, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, 0.0019932), ('gateio', 0, 2, "2021-09-01 00:10:00", "2021-09-01 04:00:00", 30.0, 0.0), ('gateio', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0009140999), ('gateio', 0, 2, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, -0.0009140999), @@ -4289,7 +4256,6 @@ def test__fetch_and_calculate_funding_fees( d2 = datetime.strptime(f"{d2} +0000", '%Y-%m-%d %H:%M:%S %z') funding_rate_history = { 'binance': funding_rate_history_octohourly, - 'ftx': funding_rate_history_hourly, 'gateio': funding_rate_history_octohourly, }[exchange][rate_start:rate_end] api_mock = MagicMock() @@ -5056,7 +5022,7 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers): exchange.get_max_leverage("BTC/USDT", 1000000000.01) -@pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'ftx', 'gateio', 'okx']) +@pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'gateio', 'okx']) def test__get_params(mocker, default_conf, exchange_name): api_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py deleted file mode 100644 index 5213c1b36..000000000 --- a/tests/exchange/test_ftx.py +++ /dev/null @@ -1,272 +0,0 @@ -from random import randint -from unittest.mock import MagicMock - -import ccxt -import pytest - -from freqtrade.exceptions import DependencyException, InvalidOrderException -from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT -from tests.conftest import get_patched_exchange - -from .test_exchange import ccxt_exceptionhandlers - - -STOPLOSS_ORDERTYPE = 'stop' - - -@pytest.mark.parametrize('order_price,exchangelimitratio,side', [ - (217.8, 1.05, "sell"), - (222.2, 0.95, "buy"), -]) -def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitratio, side): - api_mock = MagicMock() - order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) - - api_mock.create_order = MagicMock(return_value={ - 'id': order_id, - 'info': { - 'foo': 'bar' - } - }) - - 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, 'ftx') - - # stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders - order = exchange.stoploss( - pair='ETH/BTC', - amount=1, - stop_price=190, - side=side, - order_types={'stoploss_on_exchange_limit_ratio': exchangelimitratio}, - leverage=1.0 - ) - - assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' - assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE - assert api_mock.create_order.call_args_list[0][1]['side'] == side - assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 - assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params'] - assert 'stopPrice' in api_mock.create_order.call_args_list[0][1]['params'] - assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 190 - - assert api_mock.create_order.call_count == 1 - - api_mock.create_order.reset_mock() - - order = exchange.stoploss( - pair='ETH/BTC', - amount=1, - stop_price=220, - order_types={}, - side=side, - leverage=1.0 - ) - - assert 'id' in order - assert 'info' in order - assert order['id'] == order_id - assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' - assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE - assert api_mock.create_order.call_args_list[0][1]['side'] == side - assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 - assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params'] - assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220 - - api_mock.create_order.reset_mock() - order = exchange.stoploss( - pair='ETH/BTC', - amount=1, - stop_price=220, - order_types={'stoploss': 'limit'}, side=side, - leverage=1.0 - ) - - assert 'id' in order - assert 'info' in order - assert order['id'] == order_id - assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' - assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE - assert api_mock.create_order.call_args_list[0][1]['side'] == side - assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 - assert 'orderPrice' in api_mock.create_order.call_args_list[0][1]['params'] - assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == order_price - assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220 - - # test exception handling - with pytest.raises(DependencyException): - api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') - exchange.stoploss( - pair='ETH/BTC', - amount=1, - stop_price=220, - order_types={}, - side=side, - leverage=1.0 - ) - - with pytest.raises(InvalidOrderException): - api_mock.create_order = MagicMock( - side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately.")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') - exchange.stoploss( - pair='ETH/BTC', - amount=1, - stop_price=220, - order_types={}, - side=side, - leverage=1.0 - ) - - ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx", - "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}, - side=side, leverage=1.0) - - -@pytest.mark.parametrize('side', [("sell"), ("buy")]) -def test_stoploss_order_dry_run_ftx(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) - - exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') - - api_mock.create_order.reset_mock() - - order = exchange.stoploss( - pair='ETH/BTC', - amount=1, - stop_price=220, - order_types={}, - side=side, - leverage=1.0 - ) - - assert 'id' in order - assert 'info' in order - assert 'type' in order - - assert order['type'] == STOPLOSS_ORDERTYPE - assert order['price'] == 220 - assert order['amount'] == 1 - - -@pytest.mark.parametrize('sl1,sl2,sl3,side', [ - (1501, 1499, 1501, "sell"), - (1499, 1501, 1499, "buy") -]) -def test_stoploss_adjust_ftx(mocker, default_conf, sl1, sl2, sl3, side): - exchange = get_patched_exchange(mocker, default_conf, id='ftx') - order = { - 'type': STOPLOSS_ORDERTYPE, - 'price': 1500, - } - assert exchange.stoploss_adjust(sl1, order, side=side) - assert not exchange.stoploss_adjust(sl2, order, side=side) - # Test with invalid order case ... - order['type'] = 'stop_loss_limit' - assert not exchange.stoploss_adjust(sl3, order, side=side) - - -@pytest.mark.usefixtures("init_persistence") -def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order, limit_buy_order): - default_conf['dry_run'] = True - order = MagicMock() - order.myid = 123 - exchange = get_patched_exchange(mocker, default_conf, id='ftx') - exchange._dry_run_open_orders['X'] = order - assert exchange.fetch_stoploss_order('X', 'TKN/BTC').myid == 123 - - with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'): - exchange.fetch_stoploss_order('Y', 'TKN/BTC') - - default_conf['dry_run'] = False - api_mock = MagicMock() - api_mock.fetch_orders = MagicMock(return_value=[{'id': 'X', 'status': '456'}]) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx') - assert exchange.fetch_stoploss_order('X', 'TKN/BTC')['status'] == '456' - - api_mock.fetch_orders = MagicMock(return_value=[{'id': 'Y', 'status': '456'}]) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx') - with pytest.raises(InvalidOrderException, match=r"Could not get stoploss order for id X"): - exchange.fetch_stoploss_order('X', 'TKN/BTC')['status'] - - # stoploss Limit order - api_mock.fetch_orders = MagicMock(return_value=[ - {'id': 'X', 'status': 'closed', - 'info': { - 'orderId': 'mocked_limit_sell', - }}]) - api_mock.fetch_order = MagicMock(return_value=limit_sell_order.copy()) - - # No orderId field - no call to fetch_order - resp = exchange.fetch_stoploss_order('X', 'TKN/BTC') - assert resp - assert api_mock.fetch_order.call_count == 1 - assert resp['id_stop'] == 'mocked_limit_sell' - assert resp['id'] == 'X' - assert resp['type'] == 'stop' - assert resp['status_stop'] == 'triggered' - - # Stoploss market order - # Contains no new Order, but "average" instead - order = {'id': 'X', 'status': 'closed', 'info': {'orderId': None}, 'average': 0.254} - api_mock.fetch_orders = MagicMock(return_value=[order]) - api_mock.fetch_order.reset_mock() - api_mock.privateGetConditionalOrdersConditionalOrderIdTriggers = MagicMock( - return_value={'result': [ - {'orderId': 'mocked_market_sell', 'type': 'market', 'side': 'sell', 'price': 0.254} - ]}) - resp = exchange.fetch_stoploss_order('X', 'TKN/BTC') - assert resp - # fetch_order not called (no regular order ID) - assert api_mock.fetch_order.call_count == 1 - api_mock.privateGetConditionalOrdersConditionalOrderIdTriggers.call_count == 1 - expected_resp = limit_sell_order.copy() - expected_resp.update({ - 'id_stop': 'X', - 'id': 'X', - 'type': 'stop', - 'status_stop': 'triggered', - }) - assert expected_resp == resp - - with pytest.raises(InvalidOrderException): - api_mock.fetch_orders = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx') - exchange.fetch_stoploss_order(order_id='_', pair='TKN/BTC') - assert api_mock.fetch_orders.call_count == 1 - - ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'ftx', - 'fetch_stoploss_order', 'fetch_orders', - retries=API_FETCH_ORDER_RETRY_COUNT + 1, - order_id='_', pair='TKN/BTC') - - -def test_get_order_id(mocker, default_conf): - exchange = get_patched_exchange(mocker, default_conf, id='ftx') - order = { - 'type': STOPLOSS_ORDERTYPE, - 'price': 1500, - 'id': '1111', - 'id_stop': '1234', - 'info': { - } - } - assert exchange.get_order_id_conditional(order) == '1234' - - order = { - 'type': 'limit', - 'price': 1500, - 'id': '1111', - 'id_stop': '1234', - 'info': { - } - } - assert exchange.get_order_id_conditional(order) == '1111' diff --git a/tests/leverage/test_interest.py b/tests/leverage/test_interest.py index 64e99b6b4..6afa73e6a 100644 --- a/tests/leverage/test_interest.py +++ b/tests/leverage/test_interest.py @@ -19,11 +19,6 @@ twentyfive_hours = FtPrecise(25.0) ('kraken', 0.00025, ten_mins, 0.03), ('kraken', 0.00025, five_hours, 0.045), ('kraken', 0.00025, twentyfive_hours, 0.12), - # FTX - ('ftx', 0.0005, ten_mins, 0.00125), - ('ftx', 0.00025, ten_mins, 0.000625), - ('ftx', 0.00025, five_hours, 0.003125), - ('ftx', 0.00025, twentyfive_hours, 0.015625), ]) def test_interest(exchange, interest_rate, hours, expected): borrowed = FtPrecise(60.0) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 359291476..ecc1da3e3 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -612,9 +612,9 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t "lookback_timeframe": "1h", "lookback_period": 2, "refresh_period": 3600}], "BTC", "binance", ['ETH/BTC', 'LTC/BTC', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']), # ftx data is already in Quote currency, therefore won't require conversion - ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", - "lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}], - "BTC", "ftx", ['HOT/BTC', 'LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC']), + # ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", + # "lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}], + # "BTC", "ftx", ['HOT/BTC', 'LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC']), ]) def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_history, pairlists, base_currency, exchange, volumefilter_result) -> None: @@ -636,8 +636,6 @@ def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_history_high_volume['high'] = ohlcv_history_high_volume.loc[:, 'high'] * 0.01 ohlcv_history_high_volume['close'] = ohlcv_history_high_volume.loc[:, 'close'] * 0.01 - mocker.patch('freqtrade.exchange.ftx.Ftx.market_is_tradable', return_value=True) - ohlcv_data = { ('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history, ('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history, diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 10e6228f8..6b47dc1d1 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3036,7 +3036,7 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_ @pytest.mark.parametrize("is_short", [False, True]) -@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'], +@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'kraken', 'bittrex'], indirect=['limit_buy_order_canceled_empty']) def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_short, fee, limit_buy_order_canceled_empty) -> None: From a951b49541ca41cf93524259ddde695a28a3c9ef Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 14 Nov 2022 07:25:52 +0100 Subject: [PATCH 30/41] Use Generator when sending initial dataframes --- freqtrade/rpc/api_server/api_ws.py | 7 ++----- freqtrade/rpc/rpc.py | 9 +++------ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/freqtrade/rpc/api_server/api_ws.py b/freqtrade/rpc/api_server/api_ws.py index 118d70d78..785773b39 100644 --- a/freqtrade/rpc/api_server/api_ws.py +++ b/freqtrade/rpc/api_server/api_ws.py @@ -84,11 +84,8 @@ async def _process_consumer_request( # Limit the amount of candles per dataframe to 'limit' or 1500 limit = max(data.get('limit', 1500), 1500) - # They requested the full historical analyzed dataframes - analyzed_df = rpc._ws_request_analyzed_df(limit) - - # For every dataframe, send as a separate message - for _, message in analyzed_df.items(): + # For every pair in the generator, send a separate message + for message in rpc._ws_request_analyzed_df(limit): response = WSAnalyzedDFMessage(data=message) await channel_manager.send_direct(channel, response.dict(exclude_none=True)) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 143b11911..55bb5a34b 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -5,7 +5,7 @@ import logging from abc import abstractmethod from datetime import date, datetime, timedelta, timezone from math import isnan -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict, Generator, List, Optional, Tuple, Union import arrow import psutil @@ -1063,23 +1063,20 @@ class RPC: self, pairlist: List[str], limit: Optional[int] - ) -> Dict[str, Any]: + ) -> Generator[Dict[str, Any], None, None]: """ Get the analysed dataframes of each pair in the pairlist """ timeframe = self._freqtrade.config['timeframe'] candle_type = self._freqtrade.config.get('candle_type_def', CandleType.SPOT) - _data = {} for pair in pairlist: dataframe, last_analyzed = self.__rpc_analysed_dataframe_raw(pair, timeframe, limit) - _data[pair] = { + yield { "key": (pair, timeframe, candle_type), "df": dataframe, "la": last_analyzed } - return _data - def _ws_request_analyzed_df(self, limit: Optional[int]): """ Historical Analyzed Dataframes for WebSocket """ whitelist = self._freqtrade.active_pair_whitelist From f27be7ada8c5cd16c957d1831ad85c391c85f886 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 14 Nov 2022 20:52:40 +0100 Subject: [PATCH 31/41] Configure mypy to old behavior based on https://mypy-lang.blogspot.com/2022/11/mypy-0990-released.html release --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 8020b0636..2de2c957b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,8 @@ asyncio_mode = "auto" [tool.mypy] ignore_missing_imports = true +namespace_packages = false +implicit_optional = true warn_unused_ignores = true exclude = [ '^build_helpers\.py$' From 0a702cdd261e8ef3cfff1904dfe880cceba36887 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 14 Nov 2022 20:56:35 +0100 Subject: [PATCH 32/41] Ensure more methods are typechecked --- freqtrade/edge/edge_positioning.py | 4 ++-- freqtrade/freqtradebot.py | 4 ++-- freqtrade/optimize/backtesting.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 45b4cd8f1..4656b7c93 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -392,7 +392,7 @@ class Edge: # Returning a list of pairs in order of "expectancy" return final - def _find_trades_for_stoploss_range(self, df, pair, stoploss_range): + def _find_trades_for_stoploss_range(self, df, pair: str, stoploss_range) -> list: buy_column = df['enter_long'].values sell_column = df['exit_long'].values date_column = df['date'].values @@ -407,7 +407,7 @@ class Edge: return result def _detect_next_stop_or_sell_point(self, buy_column, sell_column, date_column, - ohlc_columns, stoploss, pair): + ohlc_columns, stoploss, pair: str): """ Iterate through ohlc_columns in order to find the next trade Next trade opens from the first buy signal noticed to diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ea7c2f1f9..2e2638126 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -354,7 +354,7 @@ class FreqtradeBot(LoggingMixin): if self.trading_mode == TradingMode.FUTURES: self._schedule.run_pending() - def update_closed_trades_without_assigned_fees(self): + def update_closed_trades_without_assigned_fees(self) -> None: """ Update closed trades without close fees assigned. Only acts when Orders are in the database, otherwise the last order-id is unknown. @@ -379,7 +379,7 @@ class FreqtradeBot(LoggingMixin): stoploss_order=order.ft_order_side == 'stoploss', send_msg=False) - trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() + trades = Trade.get_open_trades_without_assigned_fees() for trade in trades: if trade.is_open and not trade.fee_updated(trade.entry_side): order = trade.select_order(trade.entry_side, False) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 3436eac44..427ad121f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -166,7 +166,7 @@ class Backtesting: PairLocks.use_db = True Trade.use_db = True - def init_backtest_detail(self): + def init_backtest_detail(self) -> None: # Load detail timeframe if specified self.timeframe_detail = str(self.config.get('timeframe_detail', '')) if self.timeframe_detail: From 019577f73d06e5fca1f3b80af33d0aaab3f67d46 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 16 Nov 2022 06:36:26 +0100 Subject: [PATCH 33/41] Temporarily Downgrade cryptography until piwheels has the new wheel available --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bc84cd241..ec8b5ce7c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,8 @@ pandas-ta==0.3.14b ccxt==2.1.75 # Pin cryptography for now due to rust build errors with piwheels -cryptography==38.0.3 +cryptography==38.0.1; platform_machine == 'armv7l' +cryptography==38.0.3; platform_machine != 'armv7l' aiohttp==3.8.3 SQLAlchemy==1.4.44 python-telegram-bot==13.14 From 097af973d27608a0dc1f5f9916c81a04610e1308 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Nov 2022 07:10:47 +0100 Subject: [PATCH 34/41] improve RPC testcase to cover open orders --- tests/rpc/test_rpc.py | 86 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 54a4cbe9a..c3a0d539d 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -33,6 +33,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, + _is_dry_limit_order_filled=MagicMock(side_effect=[False, True]), ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) @@ -44,6 +45,91 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: rpc._rpc_trade_status() freqtradebot.enter_positions() + + # Open order... + results = rpc._rpc_trade_status() + assert results[0] == { + 'trade_id': 1, + 'pair': 'ETH/BTC', + 'base_currency': 'ETH', + 'quote_currency': 'BTC', + 'open_date': ANY, + 'open_timestamp': ANY, + 'is_open': ANY, + 'fee_open': ANY, + 'fee_open_cost': ANY, + 'fee_open_currency': ANY, + 'fee_close': fee.return_value, + 'fee_close_cost': ANY, + 'fee_close_currency': ANY, + 'open_rate_requested': ANY, + 'open_trade_value': 0.0010025, + 'close_rate_requested': ANY, + 'sell_reason': ANY, + 'exit_reason': ANY, + 'exit_order_status': ANY, + 'min_rate': ANY, + 'max_rate': ANY, + 'strategy': ANY, + 'buy_tag': ANY, + 'enter_tag': ANY, + 'timeframe': 5, + 'open_order_id': ANY, + 'close_date': None, + 'close_timestamp': None, + 'open_rate': 1.098e-05, + 'close_rate': None, + 'current_rate': 1.099e-05, + 'amount': 91.07468124, + 'amount_requested': 91.07468124, + 'stake_amount': 0.001, + 'trade_duration': None, + 'trade_duration_s': None, + 'close_profit': None, + 'close_profit_pct': None, + 'close_profit_abs': None, + 'current_profit': 0.0, + 'current_profit_pct': 0.0, + 'current_profit_abs': 0.0, + 'profit_ratio': 0.0, + 'profit_pct': 0.0, + 'profit_abs': 0.0, + 'profit_fiat': ANY, + 'stop_loss_abs': 0.0, + 'stop_loss_pct': None, + 'stop_loss_ratio': None, + 'stoploss_order_id': None, + 'stoploss_last_update': ANY, + 'stoploss_last_update_timestamp': ANY, + 'initial_stop_loss_abs': 0.0, + 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, + 'stoploss_current_dist': -1.099e-05, + 'stoploss_current_dist_ratio': -1.0, + 'stoploss_current_dist_pct': pytest.approx(-100.0), + 'stoploss_entry_dist': -0.0010025, + 'stoploss_entry_dist_ratio': -1.0, + 'open_order': '(limit buy rem=91.07468123)', + 'realized_profit': 0.0, + 'exchange': 'binance', + 'leverage': 1.0, + 'interest_rate': 0.0, + 'liquidation_price': None, + 'is_short': False, + 'funding_fees': 0.0, + 'trading_mode': TradingMode.SPOT, + 'orders': [{ + 'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05, + 'cost': 0.0009999999999054, 'filled': 0.0, 'ft_order_side': 'buy', + 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, + 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, + 'is_open': True, 'pair': 'ETH/BTC', 'order_id': ANY, + 'remaining': 91.07468123, 'status': ANY, 'ft_is_entry': True, + }], + } + + # Fill open order ... + freqtradebot.manage_open_orders() trades = Trade.get_open_trades() freqtradebot.exit_positions(trades) From 93addbe5c3979b6cf2bbe35c0eb8c92b3e0cfc9b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Nov 2022 10:16:38 +0000 Subject: [PATCH 35/41] Improve typechecking --- freqtrade/rpc/api_server/webserver.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index e9a12e4df..6464ae44e 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -2,7 +2,7 @@ import asyncio import logging from ipaddress import IPv4Address from threading import Thread -from typing import Any, Dict +from typing import Any, Dict, Optional import orjson import uvicorn @@ -51,9 +51,9 @@ class ApiServer(RPCHandler): # Exchange - only available in webserver mode. _exchange = None # websocket message queue stuff - _ws_channel_manager = None + _ws_channel_manager: ChannelManager _ws_thread = None - _ws_loop = None + _ws_loop: Optional[asyncio.AbstractEventLoop] = None def __new__(cls, *args, **kwargs): """ @@ -71,7 +71,7 @@ class ApiServer(RPCHandler): return self._standalone: bool = standalone self._server = None - self._ws_queue = None + self._ws_queue: Optional[ThreadedQueue] = None self._ws_background_task = None ApiServer.__initialized = True @@ -186,7 +186,7 @@ class ApiServer(RPCHandler): self._ws_background_task = asyncio.run_coroutine_threadsafe( self._broadcast_queue_data(), loop=self._ws_loop) - async def _broadcast_queue_data(self): + async def _broadcast_queue_data(self) -> None: # Instantiate the queue in this coroutine so it's attached to our loop self._ws_queue = ThreadedQueue() async_queue = self._ws_queue.async_q @@ -210,7 +210,8 @@ class ApiServer(RPCHandler): finally: # Disconnect channels and stop the loop on cancel await self._ws_channel_manager.disconnect_all() - self._ws_loop.stop() + if self._ws_loop: + self._ws_loop.stop() # Avoid adding more items to the queue if they aren't # going to get broadcasted. self._ws_queue = None From afcb86f422b1058057bb94a2b1d450f195eebbfb Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Nov 2022 10:25:51 +0000 Subject: [PATCH 36/41] Improve migration types --- freqtrade/persistence/migrations.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index a54c5570f..edbcd6be3 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -1,5 +1,5 @@ import logging -from typing import List +from typing import List, Optional from sqlalchemy import inspect, select, text, tuple_, update @@ -31,9 +31,9 @@ def get_backup_name(tabs: List[str], backup_prefix: str): return table_back_name -def get_last_sequence_ids(engine, trade_back_name, order_back_name): - order_id: int = None - trade_id: int = None +def get_last_sequence_ids(engine, trade_back_name: str, order_back_name: str): + order_id: Optional[int] = None + trade_id: Optional[int] = None if engine.name == 'postgresql': with engine.begin() as connection: From 0a7f4fd3cc6a4000106f33e50fe1862d759bde8c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Nov 2022 10:36:24 +0000 Subject: [PATCH 37/41] fix httpx test warning --- tests/rpc/test_rpc_apiserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 019b8fc82..969728b6f 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -67,7 +67,7 @@ def botclient(default_conf, mocker): def client_post(client, url, data={}): return client.post(url, - data=data, + content=data, headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS), 'Origin': 'http://example.com', 'content-type': 'application/json' From 9432bcd0654d4da287723e2d4a990bdbf50ad038 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Nov 2022 19:52:03 +0100 Subject: [PATCH 38/41] Fix telegram error on force_enter exception closes #7727 --- freqtrade/rpc/telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 92a24f024..708a1ce53 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1062,7 +1062,7 @@ class Telegram(RPCHandler): self._rpc._rpc_force_entry(pair, price, order_side=order_side) except RPCException as e: logger.exception("Forcebuy error!") - self._send_msg(str(e)) + self._send_msg(str(e), ParseMode.HTML) def _force_enter_inline(self, update: Update, _: CallbackContext) -> None: if update.callback_query: From 48e5a458561a8aa8e9ade9f4c9863d0d9e659c45 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Nov 2022 10:54:03 +0000 Subject: [PATCH 39/41] rpc_test: dont replicate whole response, updating what's changed improves readability --- tests/rpc/test_rpc.py | 257 ++++++++++++------------------------------ 1 file changed, 74 insertions(+), 183 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index c3a0d539d..8725ad2c5 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -1,6 +1,7 @@ # 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 @@ -28,113 +29,7 @@ def prec_satoshi(a, b) -> float: # Unit tests def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) - mocker.patch.multiple( - 'freqtrade.exchange.Exchange', - fetch_ticker=ticker, - get_fee=fee, - _is_dry_limit_order_filled=MagicMock(side_effect=[False, True]), - ) - - freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot) - rpc = RPC(freqtradebot) - - freqtradebot.state = State.RUNNING - with pytest.raises(RPCException, match=r'.*no active trade*'): - rpc._rpc_trade_status() - - freqtradebot.enter_positions() - - # Open order... - results = rpc._rpc_trade_status() - assert results[0] == { - 'trade_id': 1, - 'pair': 'ETH/BTC', - 'base_currency': 'ETH', - 'quote_currency': 'BTC', - 'open_date': ANY, - 'open_timestamp': ANY, - 'is_open': ANY, - 'fee_open': ANY, - 'fee_open_cost': ANY, - 'fee_open_currency': ANY, - 'fee_close': fee.return_value, - 'fee_close_cost': ANY, - 'fee_close_currency': ANY, - 'open_rate_requested': ANY, - 'open_trade_value': 0.0010025, - 'close_rate_requested': ANY, - 'sell_reason': ANY, - 'exit_reason': ANY, - 'exit_order_status': ANY, - 'min_rate': ANY, - 'max_rate': ANY, - 'strategy': ANY, - 'buy_tag': ANY, - 'enter_tag': ANY, - 'timeframe': 5, - 'open_order_id': ANY, - 'close_date': None, - 'close_timestamp': None, - 'open_rate': 1.098e-05, - 'close_rate': None, - 'current_rate': 1.099e-05, - 'amount': 91.07468124, - 'amount_requested': 91.07468124, - 'stake_amount': 0.001, - 'trade_duration': None, - 'trade_duration_s': None, - 'close_profit': None, - 'close_profit_pct': None, - 'close_profit_abs': None, - 'current_profit': 0.0, - 'current_profit_pct': 0.0, - 'current_profit_abs': 0.0, - 'profit_ratio': 0.0, - 'profit_pct': 0.0, - 'profit_abs': 0.0, - 'profit_fiat': ANY, - 'stop_loss_abs': 0.0, - 'stop_loss_pct': None, - 'stop_loss_ratio': None, - 'stoploss_order_id': None, - 'stoploss_last_update': ANY, - 'stoploss_last_update_timestamp': ANY, - 'initial_stop_loss_abs': 0.0, - 'initial_stop_loss_pct': None, - 'initial_stop_loss_ratio': None, - 'stoploss_current_dist': -1.099e-05, - 'stoploss_current_dist_ratio': -1.0, - 'stoploss_current_dist_pct': pytest.approx(-100.0), - 'stoploss_entry_dist': -0.0010025, - 'stoploss_entry_dist_ratio': -1.0, - 'open_order': '(limit buy rem=91.07468123)', - 'realized_profit': 0.0, - 'exchange': 'binance', - 'leverage': 1.0, - 'interest_rate': 0.0, - 'liquidation_price': None, - 'is_short': False, - 'funding_fees': 0.0, - 'trading_mode': TradingMode.SPOT, - 'orders': [{ - 'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05, - 'cost': 0.0009999999999054, 'filled': 0.0, 'ft_order_side': 'buy', - 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, - 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, - 'is_open': True, 'pair': 'ETH/BTC', 'order_id': ANY, - 'remaining': 91.07468123, 'status': ANY, 'ft_is_entry': True, - }], - } - - # Fill open order ... - freqtradebot.manage_open_orders() - trades = Trade.get_open_trades() - freqtradebot.exit_positions(trades) - - results = rpc._rpc_trade_status() - assert results[0] == { + gen_response = { 'trade_id': 1, 'pair': 'ETH/BTC', 'base_currency': 'ETH', @@ -213,91 +108,87 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'remaining': ANY, 'status': ANY, 'ft_is_entry': True, }], } + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker, + get_fee=fee, + _is_dry_limit_order_filled=MagicMock(side_effect=[False, True]), + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + patch_get_signal(freqtradebot) + rpc = RPC(freqtradebot) + + freqtradebot.state = State.RUNNING + with pytest.raises(RPCException, match=r'.*no active trade*'): + rpc._rpc_trade_status() + + freqtradebot.enter_positions() + + # Open order... + results = rpc._rpc_trade_status() + response_unfilled = deepcopy(gen_response) + # Different from "filled" response: + response_unfilled.update({ + 'amount': 91.07468124, + '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, + 'stoploss_current_dist': -1.099e-05, + 'stoploss_current_dist_ratio': -1.0, + 'stoploss_current_dist_pct': pytest.approx(-100.0), + 'stoploss_entry_dist': -0.0010025, + 'stoploss_entry_dist_ratio': -1.0, + 'initial_stop_loss_abs': 0.0, + 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, + 'open_order': '(limit buy rem=91.07468123)', + }) + response_unfilled['orders'][0].update({ + 'is_open': True, + 'filled': 0.0, + 'remaining': 91.07468123 + }) + + assert results[0] == response_unfilled + + # Fill open order ... + freqtradebot.manage_open_orders() + trades = Trade.get_open_trades() + freqtradebot.exit_positions(trades) + + results = rpc._rpc_trade_status() + + response = deepcopy(gen_response) + assert results[0] == response 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]['current_rate']) - assert results[0] == { - 'trade_id': 1, - 'pair': 'ETH/BTC', - 'base_currency': 'ETH', - 'quote_currency': 'BTC', - 'open_date': ANY, - 'open_timestamp': ANY, - 'is_open': ANY, - 'fee_open': ANY, - 'fee_open_cost': ANY, - 'fee_open_currency': ANY, - 'fee_close': fee.return_value, - 'fee_close_cost': ANY, - 'fee_close_currency': ANY, - 'open_rate_requested': ANY, - 'open_trade_value': ANY, - 'close_rate_requested': ANY, - 'sell_reason': ANY, - 'exit_reason': ANY, - 'exit_order_status': ANY, - 'min_rate': ANY, - 'max_rate': ANY, - 'strategy': ANY, - 'buy_tag': ANY, - 'enter_tag': ANY, - 'timeframe': ANY, - 'open_order_id': ANY, - 'close_date': None, - 'close_timestamp': None, - 'open_rate': 1.098e-05, - 'close_rate': None, - 'current_rate': ANY, - 'amount': 91.07468123, - 'amount_requested': 91.07468124, - 'trade_duration': ANY, - 'trade_duration_s': ANY, - 'stake_amount': 0.001, - 'close_profit': None, - 'close_profit_pct': None, - 'close_profit_abs': None, - 'current_profit': ANY, - 'current_profit_pct': ANY, - 'current_profit_abs': ANY, - 'profit_ratio': ANY, - 'profit_pct': ANY, - 'profit_abs': ANY, - 'profit_fiat': ANY, - 'stop_loss_abs': 9.89e-06, - 'stop_loss_pct': -10.0, - 'stop_loss_ratio': -0.1, - 'stoploss_order_id': None, - 'stoploss_last_update': ANY, - 'stoploss_last_update_timestamp': ANY, - 'initial_stop_loss_abs': 9.89e-06, - 'initial_stop_loss_pct': -10.0, - 'initial_stop_loss_ratio': -0.1, + response_norate = deepcopy(gen_response) + # Update elements that are NaN when no rate is available. + response_norate.update({ 'stoploss_current_dist': ANY, 'stoploss_current_dist_ratio': ANY, 'stoploss_current_dist_pct': ANY, - 'stoploss_entry_dist': -0.00010402, - 'stoploss_entry_dist_ratio': -0.10376381, - 'open_order': None, - 'exchange': 'binance', - 'realized_profit': 0.0, - 'leverage': 1.0, - 'interest_rate': 0.0, - 'liquidation_price': None, - 'is_short': False, - 'funding_fees': 0.0, - 'trading_mode': TradingMode.SPOT, - 'orders': [{ - 'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05, - 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', - 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, - 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, - 'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY, - 'remaining': ANY, 'status': ANY, 'ft_is_entry': True, - }], - } + '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 def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: From 1975e942d68177286f54af33904da1c86290e6af Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 1 Nov 2022 18:58:04 +0000 Subject: [PATCH 40/41] Add test for no remaining (kucoin case - https://github.com/freqtrade/freqtrade/issues/7757). --- tests/rpc/test_rpc.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 8725ad2c5..ef6c8b204 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -156,9 +156,25 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'filled': 0.0, 'remaining': 91.07468123 }) - assert results[0] == response_unfilled + # Open order without remaining + trade = Trade.get_open_trades()[0] + # kucoin case (no remaining set). + trade.orders[0].remaining = None + Trade.commit() + + results = rpc._rpc_trade_status() + # Reuse above object, only remaining changed. + response_unfilled['orders'][0].update({ + 'remaining': None + }) + assert results[0] == response_unfilled + + trade = Trade.get_open_trades()[0] + trade.orders[0].remaining = trade.amount + Trade.commit() + # Fill open order ... freqtradebot.manage_open_orders() trades = Trade.get_open_trades() From 436b314c80d2dd1ac8a38a6806d71213f5540d18 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Nov 2022 18:58:46 +0000 Subject: [PATCH 41/41] add safe_remaining fixes #7757 --- freqtrade/persistence/trade_model.py | 7 +++++++ freqtrade/rpc/rpc.py | 7 ++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index e032b6b96..19ba48fcd 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -90,6 +90,13 @@ class Order(_DECL_BASE): def safe_filled(self) -> float: return self.filled if self.filled is not None else self.amount or 0.0 + @property + def safe_remaining(self) -> float: + return ( + self.remaining if self.remaining is not None else + self.amount - (self.filled or 0.0) + ) + @property def safe_fee_base(self) -> float: return self.ft_fee_base or 0.0 diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 55bb5a34b..a0824bcc1 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -218,9 +218,10 @@ class RPC: stoploss_current_dist_pct=round(stoploss_current_dist_ratio * 100, 2), stoploss_entry_dist=stoploss_entry_dist, stoploss_entry_dist_ratio=round(stoploss_entry_dist_ratio, 8), - open_order='({} {} rem={:.8f})'.format( - order.order_type, order.side, order.remaining - ) if order else None, + open_order=( + f'({order.order_type} {order.side} rem={order.safe_remaining:.8f})' if + order else None + ), )) results.append(trade_dict) return results