From 2b456cbdeb8ac2250de382613d7488ceae1707cf Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Thu, 4 Jul 2024 10:29:13 +0200 Subject: [PATCH 001/242] Added unlock_at field for protection config --- freqtrade/configuration/config_validation.py | 27 +++++- freqtrade/constants.py | 1 + .../plugins/protections/cooldown_period.py | 5 +- freqtrade/plugins/protections/iprotection.py | 38 +++++++- .../plugins/protections/low_profit_pairs.py | 5 +- .../protections/max_drawdown_protection.py | 5 +- .../plugins/protections/stoploss_guard.py | 5 +- tests/plugins/test_protections.py | 86 ++++++++++++++++--- 8 files changed, 151 insertions(+), 21 deletions(-) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 3f8e5c9ef..597752614 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -1,6 +1,7 @@ import logging from collections import Counter from copy import deepcopy +from datetime import datetime from typing import Any, Dict from jsonschema import Draft4Validator, validators @@ -192,18 +193,40 @@ def _validate_protections(conf: Dict[str, Any]) -> None: """ for prot in conf.get("protections", []): + parsed_unlock_at = _validate_unlock_at(prot) + if "stop_duration" in prot and "stop_duration_candles" in prot: raise ConfigurationError( "Protections must specify either `stop_duration` or `stop_duration_candles`.\n" - f"Please fix the protection {prot.get('method')}" + f"Please fix the protection {prot.get('method')}." ) if "lookback_period" in prot and "lookback_period_candles" in prot: raise ConfigurationError( "Protections must specify either `lookback_period` or `lookback_period_candles`.\n" - f"Please fix the protection {prot.get('method')}" + f"Please fix the protection {prot.get('method')}." ) + if parsed_unlock_at is not None and "stop_duration" in prot: + raise ConfigurationError( + "Protections must specify either `unlock_at` or `stop_duration`.\n" + f"Please fix the protection {prot.get('method')}." + ) + + if parsed_unlock_at is not None and "stop_duration_candles" in prot: + raise ConfigurationError( + "Protections must specify either `unlock_at` or `stop_duration_candles`.\n" + f"Please fix the protection {prot.get('method')}." + ) + + +def _validate_unlock_at(config_unlock_at: str) -> datetime: + if config_unlock_at is not None and isinstance(config_unlock_at, str): + try: + return datetime.strptime(config_unlock_at, "%H:%M") + except ValueError: + raise ConfigurationError(f"Invalid date format for unlock_at: {config_unlock_at}.") + def _validate_ask_orderbook(conf: Dict[str, Any]) -> None: ask_strategy = conf.get("exit_pricing", {}) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index f8f1ac7ee..88031d65b 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -356,6 +356,7 @@ CONF_SCHEMA = { "properties": { "method": {"type": "string", "enum": AVAILABLE_PROTECTIONS}, "stop_duration": {"type": "number", "minimum": 0.0}, + "unlock_at": {"type": "string"}, "stop_duration_candles": {"type": "number", "minimum": 0}, "trade_limit": {"type": "number", "minimum": 1}, "lookback_period": {"type": "number", "minimum": 1}, diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index 2948d17d0..9b75cb50d 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -18,7 +18,10 @@ class CooldownPeriod(IProtection): """ LockReason to use """ - return f"Cooldown period for {self.stop_duration_str}." + reason = f"Cooldown period for {self.stop_duration_str}." + if self.unlock_at_str is not None: + reason += f" Unlocking trading at {self.unlock_at_str}." + return reason def short_desc(self) -> str: """ diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index 204a8b827..a3ddcfe33 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -2,7 +2,7 @@ import logging from abc import ABC, abstractmethod from dataclasses import dataclass from datetime import datetime, timedelta, timezone -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Union from freqtrade.constants import Config, LongShort from freqtrade.exchange import timeframe_to_minutes @@ -33,21 +33,33 @@ class IProtection(LoggingMixin, ABC): self._protection_config = protection_config self._stop_duration_candles: Optional[int] = None self._lookback_period_candles: Optional[int] = None + self.unlock_at: Optional[datetime] = None tf_in_min = timeframe_to_minutes(config["timeframe"]) if "stop_duration_candles" in protection_config: self._stop_duration_candles = int(protection_config.get("stop_duration_candles", 1)) self._stop_duration = tf_in_min * self._stop_duration_candles else: - self._stop_duration_candles = None self._stop_duration = int(protection_config.get("stop_duration", 60)) if "lookback_period_candles" in protection_config: self._lookback_period_candles = int(protection_config.get("lookback_period_candles", 1)) self._lookback_period = tf_in_min * self._lookback_period_candles else: - self._lookback_period_candles = None self._lookback_period = int(protection_config.get("lookback_period", 60)) + if "unlock_at" in protection_config: + now_time = datetime.now(timezone.utc) + unlock_at = datetime.strptime(protection_config["unlock_at"], "%H:%M").replace( + day=now_time.day, year=now_time.year, month=now_time.month + ) + + if unlock_at.time() < now_time.time(): + unlock_at = unlock_at.replace(day=now_time.day + 1) + + unlock_at = unlock_at.replace(tzinfo=timezone.utc) + self._stop_duration = self.calculate_timespan(now_time, unlock_at) + self.unlock_at = unlock_at + LoggingMixin.__init__(self, logger) @property @@ -80,6 +92,15 @@ class IProtection(LoggingMixin, ABC): else: return f"{self._lookback_period} {plural(self._lookback_period, 'minute', 'minutes')}" + @property + def unlock_at_str(self) -> Union[str, None]: + """ + Output configured unlock time + """ + if self.unlock_at: + return self.unlock_at.strftime("%H:%M") + return None + @abstractmethod def short_desc(self) -> str: """ @@ -118,3 +139,14 @@ class IProtection(LoggingMixin, ABC): until = max_date + timedelta(minutes=stop_minutes) return until + + @staticmethod + def calculate_timespan(start_time: datetime, end_time: datetime) -> int: + """ + Calculate the timespan between two datetime objects in minutes. + + :param start_time: The start datetime. + :param end_time: The end datetime. + :return: The difference between the two datetimes in minutes. + """ + return int((end_time - start_time).total_seconds() / 60) diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py index 360f6721c..5904ca276 100644 --- a/freqtrade/plugins/protections/low_profit_pairs.py +++ b/freqtrade/plugins/protections/low_profit_pairs.py @@ -34,10 +34,13 @@ class LowProfitPairs(IProtection): """ LockReason to use """ - return ( + reason = ( f"{profit} < {self._required_profit} in {self.lookback_period_str}, " f"locking for {self.stop_duration_str}." ) + if self.unlock_at_str is not None: + reason += f" Unlocking trading at {self.unlock_at_str}." + return reason def _low_profit( self, date_now: datetime, pair: str, side: LongShort diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index a1ba166fa..fcecdc3d0 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -37,10 +37,13 @@ class MaxDrawdown(IProtection): """ LockReason to use """ - return ( + reason = ( f"{drawdown} passed {self._max_allowed_drawdown} in {self.lookback_period_str}, " f"locking for {self.stop_duration_str}." ) + if self.unlock_at_str is not None: + reason += f" Unlocking trading at {self.unlock_at_str}." + return reason def _max_drawdown(self, date_now: datetime) -> Optional[ProtectionReturn]: """ diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index a9aca20b4..42b04fba7 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -36,10 +36,13 @@ class StoplossGuard(IProtection): """ LockReason to use """ - return ( + reason = ( f"{self._trade_limit} stoplosses in {self._lookback_period} min, " f"locking for {self._stop_duration} min." ) + if self.unlock_at_str is not None: + reason += f" Unlocking trading at {self.unlock_at_str}." + return reason def _stoploss_guard( self, date_now: datetime, pair: Optional[str], side: LongShort diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index c8a8fdf20..94bfc8d1f 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -102,56 +102,94 @@ def test_protectionmanager(mocker, default_conf): @pytest.mark.parametrize( - "timeframe,expected,protconf", + "timeframe,expected_lookback,expected_stop,protconf", [ ( "1m", - [20, 10], + 20, + 10, [{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 10}], ), ( "5m", - [100, 15], + 100, + 15, [{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 15}], ), ( "1h", - [1200, 40], + 1200, + 40, [{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 40}], ), ( "1d", - [1440, 5], + 1440, + 5, [{"method": "StoplossGuard", "lookback_period_candles": 1, "stop_duration": 5}], ), ( "1m", - [20, 5], + 20, + 5, [{"method": "StoplossGuard", "lookback_period": 20, "stop_duration_candles": 5}], ), ( "5m", - [15, 25], + 15, + 25, [{"method": "StoplossGuard", "lookback_period": 15, "stop_duration_candles": 5}], ), ( "1h", - [50, 600], + 50, + 600, [{"method": "StoplossGuard", "lookback_period": 50, "stop_duration_candles": 10}], ), ( "1h", - [60, 540], + 60, + 540, [{"method": "StoplossGuard", "lookback_period_candles": 1, "stop_duration_candles": 9}], ), + ( + "1m", + 20, + "01:00", + [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "01:00"}], + ), + ( + "5m", + 100, + "02:00", + [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "02:00"}], + ), + ( + "1h", + 1200, + "03:00", + [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "03:00"}], + ), + ( + "1d", + 1440, + "04:00", + [{"method": "StoplossGuard", "lookback_period_candles": 1, "unlock_at": "04:00"}], + ), ], ) -def test_protections_init(default_conf, timeframe, expected, protconf): +def test_protections_init(default_conf, timeframe, expected_lookback, expected_stop, protconf): + """ + Test the initialization of protections with different configurations, including unlock_at. + """ default_conf["timeframe"] = timeframe man = ProtectionManager(default_conf, protconf) assert len(man._protection_handlers) == len(protconf) - assert man._protection_handlers[0]._lookback_period == expected[0] - assert man._protection_handlers[0]._stop_duration == expected[1] + assert man._protection_handlers[0]._lookback_period == expected_lookback + if isinstance(expected_stop, int): + assert man._protection_handlers[0]._stop_duration == expected_stop + else: + assert man._protection_handlers[0].unlock_at.strftime("%H:%M") == expected_stop @pytest.mark.parametrize("is_short", [False, True]) @@ -654,6 +692,30 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): "if drawdown is > 0.0 within 20 candles.'}]", None, ), + ( + { + "method": "StoplossGuard", + "lookback_period_candles": 12, + "trade_limit": 2, + "required_profit": -0.05, + "unlock_at": "01:00", + }, + "[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, " + "2 stoplosses with profit < -5.00% within 12 candles. Unlocking trading at 01:00.'}]", + None, + ), + ( + {"method": "LowProfitPairs", "lookback_period_candles": 11, "unlock_at": "03:00"}, + "[{'LowProfitPairs': 'LowProfitPairs - Low Profit Protection, locks pairs with " + "profit < 0.0 within 11 candles. Unlocking trading at 03:00.'}]", + None, + ), + ( + {"method": "MaxDrawdown", "lookback_period_candles": 20, "unlock_at": "04:00"}, + "[{'MaxDrawdown': 'MaxDrawdown - Max drawdown protection, stop trading " + "if drawdown is > 0.0 within 20 candles. Unlocking trading at 04:00.'}]", + None, + ), ], ) def test_protection_manager_desc( From 57118691d813c5b98e38dbeaf9eff1ee0019e65f Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Thu, 4 Jul 2024 10:53:14 +0200 Subject: [PATCH 002/242] Removed entry in gitignore --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 1e96fd5da..d371c9dd9 100644 --- a/.gitignore +++ b/.gitignore @@ -115,5 +115,4 @@ target/ !config_examples/config_kraken.example.json !config_examples/config_freqai.example.json -config_examples/nfi_configs/*.json -docker-compose-*.yml \ No newline at end of file +docker-compose-*.yml From 77b4689ac8ffea0ea734d76bdfd9e77d9cfd24db Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Fri, 5 Jul 2024 22:14:35 +0200 Subject: [PATCH 003/242] Fixed implementation of unlock_at and updated unit tests --- .../plugins/protections/cooldown_period.py | 3 ++ freqtrade/plugins/protections/iprotection.py | 43 +++++++++++++------ .../plugins/protections/low_profit_pairs.py | 2 + .../protections/max_drawdown_protection.py | 2 + .../plugins/protections/stoploss_guard.py | 3 ++ tests/plugins/test_protections.py | 8 ++-- 6 files changed, 45 insertions(+), 16 deletions(-) diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index 9b75cb50d..56c790d55 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -46,6 +46,9 @@ class CooldownPeriod(IProtection): # Ignore type error as we know we only get closed trades. trade = sorted(trades, key=lambda t: t.close_date)[-1] # type: ignore self.log_once(f"Cooldown for {pair} for {self.stop_duration_str}.", logger.info) + + self.set_unlock_at_as_stop_duration() + until = self.calculate_lock_end([trade], self._stop_duration) return ProtectionReturn( diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index a3ddcfe33..c6cdd3665 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -47,18 +47,7 @@ class IProtection(LoggingMixin, ABC): else: self._lookback_period = int(protection_config.get("lookback_period", 60)) - if "unlock_at" in protection_config: - now_time = datetime.now(timezone.utc) - unlock_at = datetime.strptime(protection_config["unlock_at"], "%H:%M").replace( - day=now_time.day, year=now_time.year, month=now_time.month - ) - - if unlock_at.time() < now_time.time(): - unlock_at = unlock_at.replace(day=now_time.day + 1) - - unlock_at = unlock_at.replace(tzinfo=timezone.utc) - self._stop_duration = self.calculate_timespan(now_time, unlock_at) - self.unlock_at = unlock_at + self.set_unlock_at_as_stop_duration() LoggingMixin.__init__(self, logger) @@ -101,6 +90,36 @@ class IProtection(LoggingMixin, ABC): return self.unlock_at.strftime("%H:%M") return None + def set_unlock_at_as_stop_duration(self) -> None: + """ + Calculates the stop_duration based on the unlock_at protection config value and sets it. + """ + if "unlock_at" in self._protection_config: + self._stop_duration = self.calculate_unlock_at() + return None + + logger.warning( + "Couldn't update the stop duration, because unlock_at is not set in the " + "protection config." + ) + + def calculate_unlock_at(self) -> int: + """ + Calculate and update the stop duration based on the unlock at config. + """ + + now_time = datetime.now(timezone.utc) + unlock_at = datetime.strptime( + str(self._protection_config.get("unlock_at_config")), "%H:%M" + ).replace(day=now_time.day, year=now_time.year, month=now_time.month) + + if unlock_at.time() < now_time.time(): + unlock_at = unlock_at.replace(day=now_time.day + 1) + + self.unlock_at = unlock_at.replace(tzinfo=timezone.utc) + result = IProtection.calculate_timespan(now_time, self.unlock_at) + return result + @abstractmethod def short_desc(self) -> str: """ diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py index 5904ca276..6024fe894 100644 --- a/freqtrade/plugins/protections/low_profit_pairs.py +++ b/freqtrade/plugins/protections/low_profit_pairs.py @@ -73,6 +73,8 @@ class LowProfitPairs(IProtection): f"within {self._lookback_period} minutes.", logger.info, ) + + self.set_unlock_at_as_stop_duration() until = self.calculate_lock_end(trades, self._stop_duration) return ProtectionReturn( diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index fcecdc3d0..3f97d418e 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -73,6 +73,8 @@ class MaxDrawdown(IProtection): f" within {self.lookback_period_str}.", logger.info, ) + + self.set_unlock_at_as_stop_duration() until = self.calculate_lock_end(trades, self._stop_duration) return ProtectionReturn( diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index 42b04fba7..329b6b772 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -81,7 +81,10 @@ class StoplossGuard(IProtection): f"stoplosses within {self._lookback_period} minutes.", logger.info, ) + + self.set_unlock_at_as_stop_duration() until = self.calculate_lock_end(trades, self._stop_duration) + return ProtectionReturn( lock=True, until=until, diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 94bfc8d1f..4c76693c8 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -189,7 +189,7 @@ def test_protections_init(default_conf, timeframe, expected_lookback, expected_s if isinstance(expected_stop, int): assert man._protection_handlers[0]._stop_duration == expected_stop else: - assert man._protection_handlers[0].unlock_at.strftime("%H:%M") == expected_stop + assert man._protection_handlers[0].unlock_at_str == expected_stop @pytest.mark.parametrize("is_short", [False, True]) @@ -701,19 +701,19 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): "unlock_at": "01:00", }, "[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, " - "2 stoplosses with profit < -5.00% within 12 candles. Unlocking trading at 01:00.'}]", + "2 stoplosses with profit < -5.00% within 12 candles.'}]", None, ), ( {"method": "LowProfitPairs", "lookback_period_candles": 11, "unlock_at": "03:00"}, "[{'LowProfitPairs': 'LowProfitPairs - Low Profit Protection, locks pairs with " - "profit < 0.0 within 11 candles. Unlocking trading at 03:00.'}]", + "profit < 0.0 within 11 candles.'}]", None, ), ( {"method": "MaxDrawdown", "lookback_period_candles": 20, "unlock_at": "04:00"}, "[{'MaxDrawdown': 'MaxDrawdown - Max drawdown protection, stop trading " - "if drawdown is > 0.0 within 20 candles. Unlocking trading at 04:00.'}]", + "if drawdown is > 0.0 within 20 candles.'}]", None, ), ], From af505b346c00f496d546e3b9f6a80be55a4d816d Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Fri, 5 Jul 2024 22:17:40 +0200 Subject: [PATCH 004/242] Fixed an access on the config by a wrong config key --- freqtrade/plugins/protections/iprotection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index c6cdd3665..da0fc7a78 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -110,7 +110,7 @@ class IProtection(LoggingMixin, ABC): now_time = datetime.now(timezone.utc) unlock_at = datetime.strptime( - str(self._protection_config.get("unlock_at_config")), "%H:%M" + str(self._protection_config.get("unlock_at")), "%H:%M" ).replace(day=now_time.day, year=now_time.year, month=now_time.month) if unlock_at.time() < now_time.time(): From be894664ef59f1adbca7156e6db4600aa3ddc82b Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Sun, 14 Jul 2024 21:46:22 +0200 Subject: [PATCH 005/242] Fixed building of wrong reason texts Removed unnecessary method set_unlock_at_as_stop_duration() --- .../plugins/protections/cooldown_period.py | 24 ++++++++---- freqtrade/plugins/protections/iprotection.py | 37 ++++++------------- .../plugins/protections/low_profit_pairs.py | 15 ++++---- .../protections/max_drawdown_protection.py | 12 ++++-- .../plugins/protections/stoploss_guard.py | 15 ++++---- 5 files changed, 51 insertions(+), 52 deletions(-) diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index 56c790d55..30a611189 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -18,16 +18,23 @@ class CooldownPeriod(IProtection): """ LockReason to use """ - reason = f"Cooldown period for {self.stop_duration_str}." + reason = "Cooldown period" + if self.unlock_at_str is not None: - reason += f" Unlocking trading at {self.unlock_at_str}." - return reason + return f"{reason} until {self.unlock_at_str}." + else: + return f"{reason}of {self.stop_duration_str}." def short_desc(self) -> str: """ - Short method description - used for startup-messages + Short method description - used for startup messages """ - return f"{self.name} - Cooldown period of {self.stop_duration_str}." + result = f"{self.name} - Cooldown period " + + if self.unlock_at_str is not None: + return f"{result} until {self.unlock_at_str}." + else: + return f"{result}of {self.stop_duration_str}." def _cooldown_period(self, pair: str, date_now: datetime) -> Optional[ProtectionReturn]: """ @@ -47,9 +54,10 @@ class CooldownPeriod(IProtection): trade = sorted(trades, key=lambda t: t.close_date)[-1] # type: ignore self.log_once(f"Cooldown for {pair} for {self.stop_duration_str}.", logger.info) - self.set_unlock_at_as_stop_duration() - - until = self.calculate_lock_end([trade], self._stop_duration) + if self.unlock_at is not None: + until = self.calculate_unlock_at() + else: + until = self.calculate_lock_end([trade], self._stop_duration) return ProtectionReturn( lock=True, diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index da0fc7a78..bf86caefe 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -36,19 +36,21 @@ class IProtection(LoggingMixin, ABC): self.unlock_at: Optional[datetime] = None tf_in_min = timeframe_to_minutes(config["timeframe"]) - if "stop_duration_candles" in protection_config: - self._stop_duration_candles = int(protection_config.get("stop_duration_candles", 1)) - self._stop_duration = tf_in_min * self._stop_duration_candles + if "unlock_at" in protection_config: + self.unlock_at = self.calculate_unlock_at() else: - self._stop_duration = int(protection_config.get("stop_duration", 60)) + if "stop_duration_candles" in protection_config: + self._stop_duration_candles = int(protection_config.get("stop_duration_candles", 1)) + self._stop_duration = tf_in_min * self._stop_duration_candles + else: + self._stop_duration = int(protection_config.get("stop_duration", 60)) + if "lookback_period_candles" in protection_config: self._lookback_period_candles = int(protection_config.get("lookback_period_candles", 1)) self._lookback_period = tf_in_min * self._lookback_period_candles else: self._lookback_period = int(protection_config.get("lookback_period", 60)) - self.set_unlock_at_as_stop_duration() - LoggingMixin.__init__(self, logger) @property @@ -90,24 +92,10 @@ class IProtection(LoggingMixin, ABC): return self.unlock_at.strftime("%H:%M") return None - def set_unlock_at_as_stop_duration(self) -> None: + def calculate_unlock_at(self) -> datetime: """ - Calculates the stop_duration based on the unlock_at protection config value and sets it. + Calculate and update the unlock time based on the unlock at config. """ - if "unlock_at" in self._protection_config: - self._stop_duration = self.calculate_unlock_at() - return None - - logger.warning( - "Couldn't update the stop duration, because unlock_at is not set in the " - "protection config." - ) - - def calculate_unlock_at(self) -> int: - """ - Calculate and update the stop duration based on the unlock at config. - """ - now_time = datetime.now(timezone.utc) unlock_at = datetime.strptime( str(self._protection_config.get("unlock_at")), "%H:%M" @@ -116,9 +104,7 @@ class IProtection(LoggingMixin, ABC): if unlock_at.time() < now_time.time(): unlock_at = unlock_at.replace(day=now_time.day + 1) - self.unlock_at = unlock_at.replace(tzinfo=timezone.utc) - result = IProtection.calculate_timespan(now_time, self.unlock_at) - return result + return unlock_at.replace(tzinfo=timezone.utc) @abstractmethod def short_desc(self) -> str: @@ -156,7 +142,6 @@ class IProtection(LoggingMixin, ABC): max_date = max_date.replace(tzinfo=timezone.utc) until = max_date + timedelta(minutes=stop_minutes) - return until @staticmethod diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py index 6024fe894..b997254ab 100644 --- a/freqtrade/plugins/protections/low_profit_pairs.py +++ b/freqtrade/plugins/protections/low_profit_pairs.py @@ -34,12 +34,11 @@ class LowProfitPairs(IProtection): """ LockReason to use """ - reason = ( - f"{profit} < {self._required_profit} in {self.lookback_period_str}, " - f"locking for {self.stop_duration_str}." - ) + reason = f"{profit} < {self._required_profit} in {self.lookback_period_str}, locking" if self.unlock_at_str is not None: - reason += f" Unlocking trading at {self.unlock_at_str}." + reason += f" until {self.unlock_at_str}." + else: + reason += f" for {self.stop_duration_str}." return reason def _low_profit( @@ -74,8 +73,10 @@ class LowProfitPairs(IProtection): logger.info, ) - self.set_unlock_at_as_stop_duration() - until = self.calculate_lock_end(trades, self._stop_duration) + if self.unlock_at is not None: + until = self.calculate_unlock_at() + else: + until = self.calculate_lock_end(trades, self._stop_duration) return ProtectionReturn( lock=True, diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index 3f97d418e..264ad57d0 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -39,10 +39,12 @@ class MaxDrawdown(IProtection): """ reason = ( f"{drawdown} passed {self._max_allowed_drawdown} in {self.lookback_period_str}, " - f"locking for {self.stop_duration_str}." + f"locking " ) if self.unlock_at_str is not None: - reason += f" Unlocking trading at {self.unlock_at_str}." + reason += f" until {self.unlock_at_str}." + else: + reason += f" for {self.stop_duration_str}." return reason def _max_drawdown(self, date_now: datetime) -> Optional[ProtectionReturn]: @@ -74,8 +76,10 @@ class MaxDrawdown(IProtection): logger.info, ) - self.set_unlock_at_as_stop_duration() - until = self.calculate_lock_end(trades, self._stop_duration) + if self.unlock_at is not None: + until = self.calculate_unlock_at() + else: + until = self.calculate_lock_end(trades, self._stop_duration) return ProtectionReturn( lock=True, diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index 329b6b772..f36a2f157 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -36,12 +36,11 @@ class StoplossGuard(IProtection): """ LockReason to use """ - reason = ( - f"{self._trade_limit} stoplosses in {self._lookback_period} min, " - f"locking for {self._stop_duration} min." - ) + reason = f"{self._trade_limit} stoplosses in {self._lookback_period} min, " f"locking " if self.unlock_at_str is not None: - reason += f" Unlocking trading at {self.unlock_at_str}." + reason += f" until {self.unlock_at_str}." + else: + reason += f" for {self._stop_duration} min." return reason def _stoploss_guard( @@ -82,8 +81,10 @@ class StoplossGuard(IProtection): logger.info, ) - self.set_unlock_at_as_stop_duration() - until = self.calculate_lock_end(trades, self._stop_duration) + if self.unlock_at is not None: + until = self.calculate_unlock_at() + else: + until = self.calculate_lock_end(trades, self._stop_duration) return ProtectionReturn( lock=True, From 16dd86e732c1471c2e6b15f551e1a15bfc08581b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Jul 2024 06:28:02 +0200 Subject: [PATCH 006/242] _unlock_at should be private --- .../plugins/protections/cooldown_period.py | 2 +- freqtrade/plugins/protections/iprotection.py | 20 +++++++++---------- .../plugins/protections/low_profit_pairs.py | 2 +- .../protections/max_drawdown_protection.py | 2 +- .../plugins/protections/stoploss_guard.py | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index 30a611189..d9fb5b9a6 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -54,7 +54,7 @@ class CooldownPeriod(IProtection): trade = sorted(trades, key=lambda t: t.close_date)[-1] # type: ignore self.log_once(f"Cooldown for {pair} for {self.stop_duration_str}.", logger.info) - if self.unlock_at is not None: + if self._unlock_at is not None: until = self.calculate_unlock_at() else: until = self.calculate_lock_end([trade], self._stop_duration) diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index bf86caefe..709d03354 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -33,22 +33,22 @@ class IProtection(LoggingMixin, ABC): self._protection_config = protection_config self._stop_duration_candles: Optional[int] = None self._lookback_period_candles: Optional[int] = None - self.unlock_at: Optional[datetime] = None + self._unlock_at: Optional[datetime] = None tf_in_min = timeframe_to_minutes(config["timeframe"]) - if "unlock_at" in protection_config: - self.unlock_at = self.calculate_unlock_at() + if "stop_duration_candles" in protection_config: + self._stop_duration_candles = int(protection_config.get("stop_duration_candles", 1)) + self._stop_duration = tf_in_min * self._stop_duration_candles + elif "unlock_at" in protection_config: + self._unlock_at = self.calculate_unlock_at() else: - if "stop_duration_candles" in protection_config: - self._stop_duration_candles = int(protection_config.get("stop_duration_candles", 1)) - self._stop_duration = tf_in_min * self._stop_duration_candles - else: - self._stop_duration = int(protection_config.get("stop_duration", 60)) + self._stop_duration = int(protection_config.get("stop_duration", 60)) if "lookback_period_candles" in protection_config: self._lookback_period_candles = int(protection_config.get("lookback_period_candles", 1)) self._lookback_period = tf_in_min * self._lookback_period_candles else: + self._lookback_period_candles = None self._lookback_period = int(protection_config.get("lookback_period", 60)) LoggingMixin.__init__(self, logger) @@ -88,8 +88,8 @@ class IProtection(LoggingMixin, ABC): """ Output configured unlock time """ - if self.unlock_at: - return self.unlock_at.strftime("%H:%M") + if self._unlock_at: + return self._unlock_at.strftime("%H:%M") return None def calculate_unlock_at(self) -> datetime: diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py index b997254ab..88554d2be 100644 --- a/freqtrade/plugins/protections/low_profit_pairs.py +++ b/freqtrade/plugins/protections/low_profit_pairs.py @@ -73,7 +73,7 @@ class LowProfitPairs(IProtection): logger.info, ) - if self.unlock_at is not None: + if self._unlock_at is not None: until = self.calculate_unlock_at() else: until = self.calculate_lock_end(trades, self._stop_duration) diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index 264ad57d0..1816df303 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -76,7 +76,7 @@ class MaxDrawdown(IProtection): logger.info, ) - if self.unlock_at is not None: + if self._unlock_at is not None: until = self.calculate_unlock_at() else: until = self.calculate_lock_end(trades, self._stop_duration) diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index f36a2f157..0ef2254e8 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -81,7 +81,7 @@ class StoplossGuard(IProtection): logger.info, ) - if self.unlock_at is not None: + if self._unlock_at is not None: until = self.calculate_unlock_at() else: until = self.calculate_lock_end(trades, self._stop_duration) From 1e36bc98b94b30a46940a13124e5419baeafc6bb Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Jul 2024 06:35:32 +0200 Subject: [PATCH 007/242] chore: Remove unused method --- freqtrade/plugins/protections/iprotection.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index 709d03354..1d57b3e4d 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -143,14 +143,3 @@ class IProtection(LoggingMixin, ABC): until = max_date + timedelta(minutes=stop_minutes) return until - - @staticmethod - def calculate_timespan(start_time: datetime, end_time: datetime) -> int: - """ - Calculate the timespan between two datetime objects in minutes. - - :param start_time: The start datetime. - :param end_time: The end datetime. - :return: The difference between the two datetimes in minutes. - """ - return int((end_time - start_time).total_seconds() / 60) From d13f47ec0b80877e88f5344f0dcd500b607e702c Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Jul 2024 06:48:30 +0200 Subject: [PATCH 008/242] align wording to simplify "locking for" element --- freqtrade/plugins/protections/cooldown_period.py | 14 ++------------ freqtrade/plugins/protections/iprotection.py | 11 +++++++++++ freqtrade/plugins/protections/low_profit_pairs.py | 10 ++++------ .../plugins/protections/max_drawdown_protection.py | 9 ++------- freqtrade/plugins/protections/stoploss_guard.py | 10 ++++------ tests/plugins/test_protections.py | 4 ++-- 6 files changed, 25 insertions(+), 33 deletions(-) diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index d9fb5b9a6..3391d175a 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -18,23 +18,13 @@ class CooldownPeriod(IProtection): """ LockReason to use """ - reason = "Cooldown period" - - if self.unlock_at_str is not None: - return f"{reason} until {self.unlock_at_str}." - else: - return f"{reason}of {self.stop_duration_str}." + return f"Cooldown period for {self.unlock_reason_time_element}." def short_desc(self) -> str: """ Short method description - used for startup messages """ - result = f"{self.name} - Cooldown period " - - if self.unlock_at_str is not None: - return f"{result} until {self.unlock_at_str}." - else: - return f"{result}of {self.stop_duration_str}." + return f"{self.name} - Cooldown period {self.unlock_reason_time_element}." def _cooldown_period(self, pair: str, date_now: datetime) -> Optional[ProtectionReturn]: """ diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index 1d57b3e4d..c02fedfb2 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -32,6 +32,7 @@ class IProtection(LoggingMixin, ABC): self._config = config self._protection_config = protection_config self._stop_duration_candles: Optional[int] = None + self._stop_duration: int = 0 self._lookback_period_candles: Optional[int] = None self._unlock_at: Optional[datetime] = None @@ -92,6 +93,16 @@ class IProtection(LoggingMixin, ABC): return self._unlock_at.strftime("%H:%M") return None + @property + def unlock_reason_time_element(self) -> str: + """ + Output configured unlock time or stop duration + """ + if self.unlock_at_str is not None: + return f"until {self.unlock_at_str}" + else: + return f"for {self.stop_duration_str}" + def calculate_unlock_at(self) -> datetime: """ Calculate and update the unlock time based on the unlock at config. diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py index 88554d2be..518a20c0f 100644 --- a/freqtrade/plugins/protections/low_profit_pairs.py +++ b/freqtrade/plugins/protections/low_profit_pairs.py @@ -34,12 +34,10 @@ class LowProfitPairs(IProtection): """ LockReason to use """ - reason = f"{profit} < {self._required_profit} in {self.lookback_period_str}, locking" - if self.unlock_at_str is not None: - reason += f" until {self.unlock_at_str}." - else: - reason += f" for {self.stop_duration_str}." - return reason + return ( + f"{profit} < {self._required_profit} in {self.lookback_period_str}, " + f"locking {self.unlock_reason_time_element}." + ) def _low_profit( self, date_now: datetime, pair: str, side: LongShort diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index 1816df303..6f1c1ebf8 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -37,15 +37,10 @@ class MaxDrawdown(IProtection): """ LockReason to use """ - reason = ( + return ( f"{drawdown} passed {self._max_allowed_drawdown} in {self.lookback_period_str}, " - f"locking " + f"locking {self.unlock_reason_time_element}." ) - if self.unlock_at_str is not None: - reason += f" until {self.unlock_at_str}." - else: - reason += f" for {self.stop_duration_str}." - return reason def _max_drawdown(self, date_now: datetime) -> Optional[ProtectionReturn]: """ diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index 0ef2254e8..21e883bbd 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -36,12 +36,10 @@ class StoplossGuard(IProtection): """ LockReason to use """ - reason = f"{self._trade_limit} stoplosses in {self._lookback_period} min, " f"locking " - if self.unlock_at_str is not None: - reason += f" until {self.unlock_at_str}." - else: - reason += f" for {self._stop_duration} min." - return reason + return ( + f"{self._trade_limit} stoplosses in {self._lookback_period} min, " + f"locking {self.unlock_reason_time_element}." + ) def _stoploss_guard( self, date_now: datetime, pair: Optional[str], side: LongShort diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 4c76693c8..02252fb64 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -648,7 +648,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): ), ( {"method": "CooldownPeriod", "stop_duration": 60}, - "[{'CooldownPeriod': 'CooldownPeriod - Cooldown period of 60 minutes.'}]", + "[{'CooldownPeriod': 'CooldownPeriod - Cooldown period for 60 minutes.'}]", None, ), ( @@ -677,7 +677,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): ), ( {"method": "CooldownPeriod", "stop_duration_candles": 5}, - "[{'CooldownPeriod': 'CooldownPeriod - Cooldown period of 5 candles.'}]", + "[{'CooldownPeriod': 'CooldownPeriod - Cooldown period for 5 candles.'}]", None, ), ( From 65972d9c0cabc802842dd27c9ee6bda30261d28b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Jul 2024 06:51:31 +0200 Subject: [PATCH 009/242] Add cooldown with timeperiod test --- tests/plugins/test_protections.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 02252fb64..9d34d18fe 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -692,6 +692,14 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): "if drawdown is > 0.0 within 20 candles.'}]", None, ), + ( + { + "method": "CooldownPeriod", + "unlock_at": "01:00", + }, + "[{'CooldownPeriod': 'CooldownPeriod - Cooldown period until 01:00.'}]", + None, + ), ( { "method": "StoplossGuard", From 26aa336450219f0937018711aa2688af9b76284f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Jul 2024 07:05:42 +0200 Subject: [PATCH 010/242] Combine "until" logic into calculate_lock_end --- .../plugins/protections/cooldown_period.py | 6 +--- freqtrade/plugins/protections/iprotection.py | 29 ++++++++----------- .../plugins/protections/low_profit_pairs.py | 6 +--- .../protections/max_drawdown_protection.py | 5 +--- .../plugins/protections/stoploss_guard.py | 7 +---- 5 files changed, 16 insertions(+), 37 deletions(-) diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index 3391d175a..9608a51cc 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -43,11 +43,7 @@ class CooldownPeriod(IProtection): # Ignore type error as we know we only get closed trades. trade = sorted(trades, key=lambda t: t.close_date)[-1] # type: ignore self.log_once(f"Cooldown for {pair} for {self.stop_duration_str}.", logger.info) - - if self._unlock_at is not None: - until = self.calculate_unlock_at() - else: - until = self.calculate_lock_end([trade], self._stop_duration) + until = self.calculate_lock_end([trade]) return ProtectionReturn( lock=True, diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index c02fedfb2..f4278be77 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -103,20 +103,6 @@ class IProtection(LoggingMixin, ABC): else: return f"for {self.stop_duration_str}" - def calculate_unlock_at(self) -> datetime: - """ - Calculate and update the unlock time based on the unlock at config. - """ - now_time = datetime.now(timezone.utc) - unlock_at = datetime.strptime( - str(self._protection_config.get("unlock_at")), "%H:%M" - ).replace(day=now_time.day, year=now_time.year, month=now_time.month) - - if unlock_at.time() < now_time.time(): - unlock_at = unlock_at.replace(day=now_time.day + 1) - - return unlock_at.replace(tzinfo=timezone.utc) - @abstractmethod def short_desc(self) -> str: """ @@ -142,15 +128,24 @@ class IProtection(LoggingMixin, ABC): If true, this pair will be locked with until """ - @staticmethod - def calculate_lock_end(trades: List[LocalTrade], stop_minutes: int) -> datetime: + def calculate_lock_end(self, trades: List[LocalTrade]) -> datetime: """ Get lock end time + Implicitly uses `self._stop_duration` or `self._unlock_at` depending on the configuration. """ max_date: datetime = max([trade.close_date for trade in trades if trade.close_date]) # coming from Database, tzinfo is not set. if max_date.tzinfo is None: max_date = max_date.replace(tzinfo=timezone.utc) - until = max_date + timedelta(minutes=stop_minutes) + if self._unlock_at is not None: + # unlock_at case with fixed hour of the day + until = self._unlock_at + hour, minutes = self._unlock_at.split(":") + unlock_at = max_date.replace(hour=int(hour), minute=int(minutes)) + if unlock_at < max_date: + unlock_at += timedelta(days=1) + return unlock_at + + until = max_date + timedelta(minutes=self._stop_duration) return until diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py index 518a20c0f..f0023646a 100644 --- a/freqtrade/plugins/protections/low_profit_pairs.py +++ b/freqtrade/plugins/protections/low_profit_pairs.py @@ -70,11 +70,7 @@ class LowProfitPairs(IProtection): f"within {self._lookback_period} minutes.", logger.info, ) - - if self._unlock_at is not None: - until = self.calculate_unlock_at() - else: - until = self.calculate_lock_end(trades, self._stop_duration) + until = self.calculate_lock_end(trades) return ProtectionReturn( lock=True, diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index 6f1c1ebf8..5939ee9f0 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -71,10 +71,7 @@ class MaxDrawdown(IProtection): logger.info, ) - if self._unlock_at is not None: - until = self.calculate_unlock_at() - else: - until = self.calculate_lock_end(trades, self._stop_duration) + until = self.calculate_lock_end(trades) return ProtectionReturn( lock=True, diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index 21e883bbd..da7437178 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -78,12 +78,7 @@ class StoplossGuard(IProtection): f"stoplosses within {self._lookback_period} minutes.", logger.info, ) - - if self._unlock_at is not None: - until = self.calculate_unlock_at() - else: - until = self.calculate_lock_end(trades, self._stop_duration) - + until = self.calculate_lock_end(trades) return ProtectionReturn( lock=True, until=until, From be3fcd90e28ba9e2ee571727a696dcdffa29e60c Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Jul 2024 07:14:33 +0200 Subject: [PATCH 011/242] Remove unneeded property --- freqtrade/plugins/protections/iprotection.py | 19 +++++-------------- tests/plugins/test_protections.py | 2 +- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index f4278be77..670a803c4 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -2,7 +2,7 @@ import logging from abc import ABC, abstractmethod from dataclasses import dataclass from datetime import datetime, timedelta, timezone -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional from freqtrade.constants import Config, LongShort from freqtrade.exchange import timeframe_to_minutes @@ -34,14 +34,14 @@ class IProtection(LoggingMixin, ABC): self._stop_duration_candles: Optional[int] = None self._stop_duration: int = 0 self._lookback_period_candles: Optional[int] = None - self._unlock_at: Optional[datetime] = None + self._unlock_at: Optional[str] = None tf_in_min = timeframe_to_minutes(config["timeframe"]) if "stop_duration_candles" in protection_config: self._stop_duration_candles = int(protection_config.get("stop_duration_candles", 1)) self._stop_duration = tf_in_min * self._stop_duration_candles elif "unlock_at" in protection_config: - self._unlock_at = self.calculate_unlock_at() + self._unlock_at = protection_config.get("unlock_at") else: self._stop_duration = int(protection_config.get("stop_duration", 60)) @@ -84,22 +84,13 @@ class IProtection(LoggingMixin, ABC): else: return f"{self._lookback_period} {plural(self._lookback_period, 'minute', 'minutes')}" - @property - def unlock_at_str(self) -> Union[str, None]: - """ - Output configured unlock time - """ - if self._unlock_at: - return self._unlock_at.strftime("%H:%M") - return None - @property def unlock_reason_time_element(self) -> str: """ Output configured unlock time or stop duration """ - if self.unlock_at_str is not None: - return f"until {self.unlock_at_str}" + if self._unlock_at is not None: + return f"until {self._unlock_at}" else: return f"for {self.stop_duration_str}" diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 9d34d18fe..c537eb035 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -189,7 +189,7 @@ def test_protections_init(default_conf, timeframe, expected_lookback, expected_s if isinstance(expected_stop, int): assert man._protection_handlers[0]._stop_duration == expected_stop else: - assert man._protection_handlers[0].unlock_at_str == expected_stop + assert man._protection_handlers[0]._unlock_at == expected_stop @pytest.mark.parametrize("is_short", [False, True]) From a3c52445ee740b66bb85bba3997ae9d658e79688 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Jul 2024 07:14:46 +0200 Subject: [PATCH 012/242] Simplify validation --- freqtrade/configuration/config_validation.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 597752614..4bb260b52 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -207,15 +207,12 @@ def _validate_protections(conf: Dict[str, Any]) -> None: f"Please fix the protection {prot.get('method')}." ) - if parsed_unlock_at is not None and "stop_duration" in prot: + if parsed_unlock_at is not None and ( + "stop_duration" in prot or "stop_duration_candles" in prot + ): raise ConfigurationError( - "Protections must specify either `unlock_at` or `stop_duration`.\n" - f"Please fix the protection {prot.get('method')}." - ) - - if parsed_unlock_at is not None and "stop_duration_candles" in prot: - raise ConfigurationError( - "Protections must specify either `unlock_at` or `stop_duration_candles`.\n" + "Protections must specify either `unlock_at`, `stop_duration` or " + "`stop_duration_candles`.\n" f"Please fix the protection {prot.get('method')}." ) From d590ab003f34f91742d013dd1e99a2ec319cb327 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Jul 2024 07:26:41 +0200 Subject: [PATCH 013/242] Add unlock_at config test, simplify validation --- freqtrade/configuration/config_validation.py | 15 ++++++--------- tests/test_configuration.py | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 4bb260b52..aa9a1757d 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -193,7 +193,12 @@ def _validate_protections(conf: Dict[str, Any]) -> None: """ for prot in conf.get("protections", []): - parsed_unlock_at = _validate_unlock_at(prot) + parsed_unlock_at = None + if (config_unlock_at := prot.get("unlock_at")) is not None: + try: + parsed_unlock_at = datetime.strptime(config_unlock_at, "%H:%M") + except ValueError: + raise ConfigurationError(f"Invalid date format for unlock_at: {config_unlock_at}.") if "stop_duration" in prot and "stop_duration_candles" in prot: raise ConfigurationError( @@ -217,14 +222,6 @@ def _validate_protections(conf: Dict[str, Any]) -> None: ) -def _validate_unlock_at(config_unlock_at: str) -> datetime: - if config_unlock_at is not None and isinstance(config_unlock_at, str): - try: - return datetime.strptime(config_unlock_at, "%H:%M") - except ValueError: - raise ConfigurationError(f"Invalid date format for unlock_at: {config_unlock_at}.") - - def _validate_ask_orderbook(conf: Dict[str, Any]) -> None: ask_strategy = conf.get("exit_pricing", {}) ob_min = ask_strategy.get("order_book_min") diff --git a/tests/test_configuration.py b/tests/test_configuration.py index f9368246a..af482a965 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -840,6 +840,21 @@ def test_validate_whitelist(default_conf): ], r"Protections must specify either `stop_duration`.*", ), + ( + [ + { + "method": "StoplossGuard", + "lookback_period": 20, + "stop_duration": 10, + "unlock_at": "20:02", + } + ], + r"Protections must specify either `unlock_at`, `stop_duration` or.*", + ), + ( + [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "20:02"}], + None, + ), ], ) def test_validate_protections(default_conf, protconf, expected): From dcc9d20ccae24116fac6cb7b66d5de1a6f3fd855 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Jul 2024 07:31:11 +0200 Subject: [PATCH 014/242] Remove unnecessary statement --- freqtrade/plugins/protections/iprotection.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index 670a803c4..c4b039161 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -131,7 +131,6 @@ class IProtection(LoggingMixin, ABC): if self._unlock_at is not None: # unlock_at case with fixed hour of the day - until = self._unlock_at hour, minutes = self._unlock_at.split(":") unlock_at = max_date.replace(hour=int(hour), minute=int(minutes)) if unlock_at < max_date: From f714d1ab2849a3566a28739882f8d4eeb57e0175 Mon Sep 17 00:00:00 2001 From: simwai <16225108+simwai@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:08:12 +0200 Subject: [PATCH 015/242] Added unlock_at field to protections document --- docs/includes/protections.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/includes/protections.md b/docs/includes/protections.md index 12af081c0..e64ca0328 100644 --- a/docs/includes/protections.md +++ b/docs/includes/protections.md @@ -36,6 +36,7 @@ All protection end times are rounded up to the next candle to avoid sudden, unex | `lookback_period_candles` | Only trades that completed within the last `lookback_period_candles` candles will be considered. This setting may be ignored by some Protections.
**Datatype:** Positive integer (in candles). | `lookback_period` | Only trades that completed after `current_time - lookback_period` will be considered.
Cannot be used together with `lookback_period_candles`.
This setting may be ignored by some Protections.
**Datatype:** Float (in minutes) | `trade_limit` | Number of trades required at minimum (not used by all Protections).
**Datatype:** Positive integer +| `unlock_at` | Time when trading will be unlocked regularly (not used by all Protections).
**Datatype:** string
**Input Format:** "HH:MM" (24-hours) !!! Note "Durations" Durations (`stop_duration*` and `lookback_period*` can be defined in either minutes or candles). @@ -44,7 +45,7 @@ All protection end times are rounded up to the next candle to avoid sudden, unex #### Stoploss Guard `StoplossGuard` selects all trades within `lookback_period` in minutes (or in candles when using `lookback_period_candles`). -If `trade_limit` or more trades resulted in stoploss, trading will stop for `stop_duration` in minutes (or in candles when using `stop_duration_candles`). +If `trade_limit` or more trades resulted in stoploss, trading will stop for `stop_duration` in minutes (or in candles when using `stop_duration_candles`, or until the set time when using `unlock_at`). This applies across all pairs, unless `only_per_pair` is set to true, which will then only look at one pair at a time. @@ -97,7 +98,7 @@ def protections(self): #### Low Profit Pairs `LowProfitPairs` uses all trades for a pair within `lookback_period` in minutes (or in candles when using `lookback_period_candles`) to determine the overall profit ratio. -If that ratio is below `required_profit`, that pair will be locked for `stop_duration` in minutes (or in candles when using `stop_duration_candles`). +If that ratio is below `required_profit`, that pair will be locked for `stop_duration` in minutes (or in candles when using `stop_duration_candles`, or until the set time when using `unlock_at`). For futures bots, setting `only_per_side` will make the bot only consider one side, and will then only lock this one side, allowing for example shorts to continue after a series of long losses. @@ -120,7 +121,7 @@ def protections(self): #### Cooldown Period -`CooldownPeriod` locks a pair for `stop_duration` in minutes (or in candles when using `stop_duration_candles`) after selling, avoiding a re-entry for this pair for `stop_duration` minutes. +`CooldownPeriod` locks a pair for `stop_duration` in minutes (or in candles when using `stop_duration_candles`, or until the set time when using `unlock_at`) after selling, avoiding a re-entry for this pair for `stop_duration` minutes. The below example will stop trading a pair for 2 candles after closing a trade, allowing this pair to "cool down". From 4b1177e07e48007da7721984a9eb25c482f83a93 Mon Sep 17 00:00:00 2001 From: jainanuj94 Date: Wed, 24 Jul 2024 19:09:45 +0530 Subject: [PATCH 016/242] 10348 | Create new pair list to dynamically fetch pairs based on percent volume change --- .../pairlist/PercentVolumeChangePairList.py | 316 +++++++++++++++ .../test_percentvolumechangepairlist.py | 376 ++++++++++++++++++ 2 files changed, 692 insertions(+) create mode 100644 freqtrade/plugins/pairlist/PercentVolumeChangePairList.py create mode 100644 tests/plugins/test_percentvolumechangepairlist.py diff --git a/freqtrade/plugins/pairlist/PercentVolumeChangePairList.py b/freqtrade/plugins/pairlist/PercentVolumeChangePairList.py new file mode 100644 index 000000000..0aee37959 --- /dev/null +++ b/freqtrade/plugins/pairlist/PercentVolumeChangePairList.py @@ -0,0 +1,316 @@ +""" +Change PairList provider + +Provides dynamic pair list based on trade change +sorted based on percentage change in volume over a +defined period +""" +import logging +from datetime import timedelta +from typing import Any, Dict, List, Literal + +from cachetools import TTLCache + +from freqtrade.constants import ListPairsWithTimeframes +from freqtrade.exceptions import OperationalException +from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date +from freqtrade.exchange.types import Tickers +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting +from freqtrade.util import dt_now, format_ms_time + + +logger = logging.getLogger(__name__) + +SORT_VALUES = ["rolling_volume_change"] + + +class PercentVolumeChangePairList(IPairList): + is_pairlist_generator = True + supports_backtesting = SupportsBacktesting.NO + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + if "number_assets" not in self._pairlistconfig: + raise OperationalException( + "`number_assets` not specified. Please check your configuration " + 'for "pairlist.config.number_assets"' + ) + + self._stake_currency = self._config["stake_currency"] + self._number_pairs = self._pairlistconfig["number_assets"] + self._sort_key: Literal["rolling_volume_change"] = self._pairlistconfig.get( + "sort_key", "rolling_volume_change" + ) + self._min_value = self._pairlistconfig.get("min_value", 0) + self._max_value = self._pairlistconfig.get("max_value", None) + self._refresh_period = self._pairlistconfig.get("refresh_period", 1800) + self._pair_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) + self._lookback_days = self._pairlistconfig.get("lookback_days", 0) + self._lookback_timeframe = self._pairlistconfig.get("lookback_timeframe", "1d") + self._lookback_period = self._pairlistconfig.get("lookback_period", 0) + self._def_candletype = self._config["candle_type_def"] + + if (self._lookback_days > 0) & (self._lookback_period > 0): + raise OperationalException( + "Ambiguous configuration: lookback_days and lookback_period both set in pairlist " + "config. Please set lookback_days only or lookback_period and lookback_timeframe " + "and restart the bot." + ) + + # overwrite lookback timeframe and days when lookback_days is set + if self._lookback_days > 0: + self._lookback_timeframe = "1d" + self._lookback_period = self._lookback_days + + # get timeframe in minutes and seconds + self._tf_in_min = timeframe_to_minutes(self._lookback_timeframe) + _tf_in_sec = self._tf_in_min * 60 + + # whether to use range lookback or not + self._use_range = (self._tf_in_min > 0) & (self._lookback_period > 0) + + if self._use_range & (self._refresh_period < _tf_in_sec): + raise OperationalException( + f"Refresh period of {self._refresh_period} seconds is smaller than one " + f"timeframe of {self._lookback_timeframe}. Please adjust refresh_period " + f"to at least {_tf_in_sec} and restart the bot." + ) + + if not self._use_range and not ( + self._exchange.exchange_has("fetchTickers") + and self._exchange.get_option("tickers_have_change") + ): + raise OperationalException( + "Exchange does not support dynamic whitelist in this configuration. " + "Please edit your config and either remove PercentVolumeChangePairList, " + "or switch to using candles. and restart the bot." + ) + + if not self._validate_keys(self._sort_key): + raise OperationalException(f"key {self._sort_key} not in {SORT_VALUES}") + + candle_limit = self._exchange.ohlcv_candle_limit( + self._lookback_timeframe, self._config["candle_type_def"] + ) + if self._lookback_period < 4: + raise OperationalException("ChangeFilter requires lookback_period to be >= 4") + self.log_once(f"Candle limit is {candle_limit}", logger.info) + if self._lookback_period > candle_limit: + raise OperationalException( + "ChangeFilter requires lookback_period to not " + f"exceed exchange max request size ({candle_limit})" + ) + + @property + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requires tickers, an empty Dict is passed + as tickers argument to filter_pairlist + """ + return not self._use_range + + def _validate_keys(self, key): + return key in SORT_VALUES + + def short_desc(self) -> str: + """ + Short whitelist method description - used for startup-messages + """ + return (f"{self.name} - top {self._pairlistconfig['number_assets']} percent " + f"volume change pairs.") + + @staticmethod + def description() -> str: + return "Provides dynamic pair list based on percentage volume change." + + @staticmethod + def available_parameters() -> Dict[str, PairlistParameter]: + return { + "number_assets": { + "type": "number", + "default": 30, + "description": "Number of assets", + "help": "Number of assets to use from the pairlist", + }, + "sort_key": { + "type": "option", + "default": "rolling_volume_change", + "options": SORT_VALUES, + "description": "Sort key", + "help": "Sort key to use for sorting the pairlist.", + }, + "min_value": { + "type": "number", + "default": 0, + "description": "Minimum value", + "help": "Minimum value to use for filtering the pairlist.", + }, + "max_value": { + "type": "number", + "default": None, + "description": "Maximum value", + "help": "Maximum value to use for filtering the pairlist.", + }, + "refresh_period": { + "type": "number", + "default": 1800, + "description": "Refresh period", + "help": "Refresh period in seconds", + }, + "lookback_days": { + "type": "number", + "default": 0, + "description": "Lookback Days", + "help": "Number of days to look back at.", + }, + "lookback_timeframe": { + "type": "string", + "default": "1d", + "description": "Lookback Timeframe", + "help": "Timeframe to use for lookback.", + }, + "lookback_period": { + "type": "number", + "default": 0, + "description": "Lookback Period", + "help": "Number of periods to look back at.", + }, + } + + def gen_pairlist(self, tickers: Tickers) -> List[str]: + """ + Generate the pairlist + :param tickers: Tickers (from exchange.get_tickers). May be cached. + :return: List of pairs + """ + # Generate dynamic whitelist + # Must always run if this pairlist is not the first in the list. + pairlist = self._pair_cache.get("pairlist") + if pairlist: + # Item found - no refresh necessary + return pairlist.copy() + else: + # Use fresh pairlist + # Check if pair quote currency equals to the stake currency. + _pairlist = [ + k + for k in self._exchange.get_markets( + quote_currencies=[self._stake_currency], tradable_only=True, active_only=True + ).keys() + ] + + # No point in testing for blacklisted pairs... + _pairlist = self.verify_blacklist(_pairlist, logger.info) + if not self._use_range: + filtered_tickers = [ + v + for k, v in tickers.items() + if ( + self._exchange.get_pair_quote_currency(k) == self._stake_currency + and (self._use_range or v.get(self._sort_key) is not None) + and v["symbol"] in _pairlist + ) + ] + pairlist = [s["symbol"] for s in filtered_tickers] + else: + pairlist = _pairlist + + pairlist = self.filter_pairlist(pairlist, tickers) + self._pair_cache["pairlist"] = pairlist.copy() + + return pairlist + + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: + """ + Filters and sorts pairlist and returns the whitelist again. + Called on each bot iteration - please use internal caching if necessary + :param pairlist: pairlist to filter or sort + :param tickers: Tickers (from exchange.get_tickers). May be cached. + :return: new whitelist + """ + self.log_once(f"Filter ticker is self use range {pairlist}", logger.warning) + if self._use_range: + filtered_tickers: List[Dict[str, Any]] = [{"symbol": k} for k in pairlist] + + # get lookback period in ms, for exchange ohlcv fetch + since_ms = ( + int( + timeframe_to_prev_date( + self._lookback_timeframe, + dt_now() + + timedelta( + minutes=-(self._lookback_period * self._tf_in_min) - self._tf_in_min + ), + ).timestamp() + ) + * 1000 + ) + + to_ms = ( + int( + timeframe_to_prev_date( + self._lookback_timeframe, dt_now() - timedelta(minutes=self._tf_in_min) + ).timestamp() + ) + * 1000 + ) + + # todo: utc date output for starting date + self.log_once( + f"Using change range of {self._lookback_period} candles, timeframe: " + f"{self._lookback_timeframe}, starting from {format_ms_time(since_ms)} " + f"till {format_ms_time(to_ms)}", + logger.info, + ) + needed_pairs: ListPairsWithTimeframes = [ + (p, self._lookback_timeframe, self._def_candletype) + for p in [s["symbol"] for s in filtered_tickers] + if p not in self._pair_cache + ] + + candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms) + + for i, p in enumerate(filtered_tickers): + pair_candles = ( + candles[(p["symbol"], self._lookback_timeframe, self._def_candletype)] + if (p["symbol"], self._lookback_timeframe, self._def_candletype) in candles + else None + ) + + # in case of candle data calculate typical price and change for candle + if pair_candles is not None and not pair_candles.empty: + pair_candles["rolling_volume_sum"] = ( + pair_candles["volume"].rolling(window=self._lookback_period).sum() + ) + pair_candles["rolling_volume_change"] = ( + pair_candles["rolling_volume_sum"].pct_change() * 100 + ) + + # ensure that a rolling sum over the lookback_period is built + # if pair_candles contains more candles than lookback_period + rolling_volume_change = pair_candles["rolling_volume_change"].fillna(0).iloc[-1] + + # replace change with a range change sum calculated above + filtered_tickers[i]["rolling_volume_change"] = rolling_volume_change + self.log_once(f"ticker {filtered_tickers[i]}", logger.info) + else: + filtered_tickers[i]["rolling_volume_change"] = 0 + else: + filtered_tickers = [v for k, v in tickers.items() if k in pairlist] + + filtered_tickers = [v for v in filtered_tickers if v[self._sort_key] > self._min_value] + if self._max_value is not None: + filtered_tickers = [v for v in filtered_tickers if v[self._sort_key] < self._max_value] + + sorted_tickers = sorted(filtered_tickers, reverse=True, key=lambda t: t[self._sort_key]) + + self.log_once(f"Sorted Tickers {sorted_tickers}", logger.info) + # Validate whitelist to only have active market pairs + pairs = self._whitelist_for_active_markets([s["symbol"] for s in sorted_tickers]) + pairs = self.verify_blacklist(pairs, logmethod=logger.info) + # Limit pairlist to the requested number of pairs + pairs = pairs[: self._number_pairs] + + return pairs diff --git a/tests/plugins/test_percentvolumechangepairlist.py b/tests/plugins/test_percentvolumechangepairlist.py new file mode 100644 index 000000000..b09307151 --- /dev/null +++ b/tests/plugins/test_percentvolumechangepairlist.py @@ -0,0 +1,376 @@ +from datetime import datetime, timezone +from unittest.mock import MagicMock + +import pandas as pd +import pytest + +from freqtrade.data.converter import ohlcv_to_dataframe +from freqtrade.enums import CandleType +from freqtrade.exceptions import OperationalException +from freqtrade.plugins.pairlist.PercentVolumeChangePairList import PercentVolumeChangePairList +from freqtrade.plugins.pairlistmanager import PairListManager +from tests.conftest import ( + EXMS, + generate_test_data_raw, + get_patched_exchange, + get_patched_freqtradebot, +) + + +@pytest.fixture(scope="function") +def rpl_config(default_conf): + default_conf["stake_currency"] = "USDT" + + default_conf["exchange"]["pair_whitelist"] = [ + "ETH/USDT", + "XRP/USDT", + ] + default_conf["exchange"]["pair_blacklist"] = ["BLK/USDT"] + + return default_conf + + +def test_volume_change_pair_list_init_exchange_support(mocker, rpl_config): + rpl_config["pairlists"] = [ + { + "method": "PercentVolumeChangePairList", + "number_assets": 2, + "sort_key": "rolling_volume_change", + "min_value": 0, + "refresh_period": 86400, + } + ] + + with pytest.raises( + OperationalException, + match=r"Exchange does not support dynamic whitelist in this configuration. " + r"Please edit your config and either remove PercentVolumeChangePairList, " + r"or switch to using candles. and restart the bot.", + ): + get_patched_freqtradebot(mocker, rpl_config) + + +def test_volume_change_pair_list_init_wrong_refresh_period(mocker, rpl_config): + rpl_config["pairlists"] = [ + { + "method": "PercentVolumeChangePairList", + "number_assets": 2, + "sort_key": "rolling_volume_change", + "min_value": 0, + "refresh_period": 1800, + "lookback_days": 4, + } + ] + + with pytest.raises( + OperationalException, + match=r"Refresh period of 1800 seconds is smaller than one " + r"timeframe of 1d. Please adjust refresh_period " + r"to at least 86400 and restart the bot.", + ): + get_patched_freqtradebot(mocker, rpl_config) + + +def test_volume_change_pair_list_init_wrong_lookback_period(mocker, rpl_config): + rpl_config["pairlists"] = [ + { + "method": "PercentVolumeChangePairList", + "number_assets": 2, + "sort_key": "rolling_volume_change", + "min_value": 0, + "refresh_period": 86400, + "lookback_days": 3, + "lookback_period": 3, + } + ] + + with pytest.raises( + OperationalException, + match=r"Ambiguous configuration: lookback_days " + r"and lookback_period both set in pairlist config. " + r"Please set lookback_days only or lookback_period " + r"and lookback_timeframe and restart the bot.", + ): + get_patched_freqtradebot(mocker, rpl_config) + + rpl_config["pairlists"] = [ + { + "method": "PercentVolumeChangePairList", + "number_assets": 2, + "sort_key": "rolling_volume_change", + "min_value": 0, + "refresh_period": 86400, + "lookback_days": 3, + } + ] + + with pytest.raises( + OperationalException, match=r"ChangeFilter requires lookback_period to be >= 4" + ): + get_patched_freqtradebot(mocker, rpl_config) + + rpl_config["pairlists"] = [ + { + "method": "PercentVolumeChangePairList", + "number_assets": 2, + "sort_key": "rolling_volume_change", + "min_value": 0, + "refresh_period": 86400, + "lookback_period": 3, + } + ] + + with pytest.raises( + OperationalException, match=r"ChangeFilter requires lookback_period to be >= 4" + ): + get_patched_freqtradebot(mocker, rpl_config) + + rpl_config["pairlists"] = [ + { + "method": "PercentVolumeChangePairList", + "number_assets": 2, + "sort_key": "rolling_volume_change", + "min_value": 0, + "refresh_period": 86400, + "lookback_days": 1001, + } + ] + + with pytest.raises( + OperationalException, + match=r"ChangeFilter requires lookback_period to not exceed" + r" exchange max request size \(1000\)", + ): + get_patched_freqtradebot(mocker, rpl_config) + + +def test_volume_change_pair_list_init_wrong_config(mocker, rpl_config): + rpl_config["pairlists"] = [ + { + "method": "PercentVolumeChangePairList", + "sort_key": "rolling_volume_change", + "min_value": 0, + "refresh_period": 86400, + } + ] + + with pytest.raises( + OperationalException, + match=r"`number_assets` not specified. Please check your configuration " + r'for "pairlist.config.number_assets"', + ): + get_patched_freqtradebot(mocker, rpl_config) + + +def test_gen_pairlist_with_valid_change_pair_list_config(mocker, rpl_config, tickers, time_machine): + rpl_config["pairlists"] = [ + { + "method": "PercentVolumeChangePairList", + "number_assets": 2, + "sort_key": "rolling_volume_change", + "min_value": 0, + "refresh_period": 86400, + "lookback_days": 4, + } + ] + start = datetime(2024, 8, 1, 0, 0, 0, 0, tzinfo=timezone.utc) + time_machine.move_to(start, tick=False) + + mock_ohlcv_data = { + ("ETH/USDT", "1d", CandleType.SPOT): pd.DataFrame( + ohlcv_to_dataframe( + generate_test_data_raw("1d", 100, start.strftime("%Y-%m-%d"), random_seed=12), + "1d", + pair="ETH/USDT", + fill_missing=True, + ) + ), + ("BTC/USDT", "1d", CandleType.SPOT): pd.DataFrame( + ohlcv_to_dataframe( + generate_test_data_raw("1d", 100, start.strftime("%Y-%m-%d"), random_seed=13), + "1d", + pair="BTC/USDT", + fill_missing=True, + ) + ), + ("XRP/USDT", "1d", CandleType.SPOT): pd.DataFrame( + ohlcv_to_dataframe( + generate_test_data_raw("1d", 100, start.strftime("%Y-%m-%d"), random_seed=14), + "1d", + pair="XRP/USDT", + fill_missing=True, + ) + ), + ("NEO/USDT", "1d", CandleType.SPOT): pd.DataFrame( + ohlcv_to_dataframe( + generate_test_data_raw("1d", 100, start.strftime("%Y-%m-%d"), random_seed=15), + "1d", + pair="NEO/USDT", + fill_missing=True, + ) + ), + ("TKN/USDT", "1d", CandleType.SPOT): pd.DataFrame( + # Make sure always have highest rolling_volume_change + { + "timestamp": [ + "2024-07-01 00:00:00", + "2024-07-01 01:00:00", + "2024-07-01 02:00:00", + "2024-07-01 03:00:00", + "2024-07-01 04:00:00", + "2024-07-01 05:00:00", + ], + "open": [100, 102, 101, 103, 104, 105], + "high": [102, 103, 102, 104, 105, 106], + "low": [99, 101, 100, 102, 103, 104], + "close": [101, 102, 103, 104, 105, 106], + "volume": [1000, 1500, 2000, 2500, 3000, 3500], + } + ), + } + + mocker.patch(f"{EXMS}.refresh_latest_ohlcv", MagicMock(return_value=mock_ohlcv_data)) + + exchange = get_patched_exchange(mocker, rpl_config, exchange="binance") + pairlistmanager = PairListManager(exchange, rpl_config) + + remote_pairlist = PercentVolumeChangePairList( + exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0 + ) + + result = remote_pairlist.gen_pairlist(tickers) + + assert len(result) == 2 + assert result == ["TKN/USDT", "BTC/USDT"] + + +def test_filter_pairlist_with_empty_ticker(mocker, rpl_config, tickers, time_machine): + rpl_config["pairlists"] = [ + { + "method": "PercentVolumeChangePairList", + "number_assets": 2, + "sort_key": "rolling_volume_change", + "min_value": 0, + "refresh_period": 86400, + "lookback_days": 4, + } + ] + start = datetime(2024, 8, 1, 0, 0, 0, 0, tzinfo=timezone.utc) + time_machine.move_to(start, tick=False) + + mock_ohlcv_data = { + ("ETH/USDT", "1d", CandleType.SPOT): pd.DataFrame( + { + "timestamp": [ + "2024-07-01 00:00:00", + "2024-07-01 01:00:00", + "2024-07-01 02:00:00", + "2024-07-01 03:00:00", + "2024-07-01 04:00:00", + "2024-07-01 05:00:00", + ], + "open": [100, 102, 101, 103, 104, 105], + "high": [102, 103, 102, 104, 105, 106], + "low": [99, 101, 100, 102, 103, 104], + "close": [101, 102, 103, 104, 105, 106], + "volume": [1000, 1500, 2000, 2500, 3000, 3500], + } + ), + ("XRP/USDT", "1d", CandleType.SPOT): pd.DataFrame( + { + "timestamp": [ + "2024-07-01 00:00:00", + "2024-07-01 01:00:00", + "2024-07-01 02:00:00", + "2024-07-01 03:00:00", + "2024-07-01 04:00:00", + "2024-07-01 05:00:00", + ], + "open": [100, 102, 101, 103, 104, 105], + "high": [102, 103, 102, 104, 105, 106], + "low": [99, 101, 100, 102, 103, 104], + "close": [101, 102, 103, 104, 105, 106], + "volume": [1000, 1500, 2000, 2500, 3000, 3500], + } + ), + } + + mocker.patch(f"{EXMS}.refresh_latest_ohlcv", MagicMock(return_value=mock_ohlcv_data)) + exchange = get_patched_exchange(mocker, rpl_config, exchange="binance") + pairlistmanager = PairListManager(exchange, rpl_config) + + remote_pairlist = PercentVolumeChangePairList( + exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0 + ) + + result = remote_pairlist.filter_pairlist(rpl_config["exchange"]["pair_whitelist"], {}) + + assert len(result) == 2 + assert result == ["ETH/USDT", "XRP/USDT"] + + +def test_filter_pairlist_with_max_value_set(mocker, rpl_config, tickers, time_machine): + rpl_config["pairlists"] = [ + { + "method": "PercentVolumeChangePairList", + "number_assets": 2, + "sort_key": "rolling_volume_change", + "min_value": 0, + "max_value": 15, + "refresh_period": 86400, + "lookback_days": 4, + } + ] + + start = datetime(2024, 8, 1, 0, 0, 0, 0, tzinfo=timezone.utc) + time_machine.move_to(start, tick=False) + + mock_ohlcv_data = { + ("ETH/USDT", "1d", CandleType.SPOT): pd.DataFrame( + { + "timestamp": [ + "2024-07-01 00:00:00", + "2024-07-01 01:00:00", + "2024-07-01 02:00:00", + "2024-07-01 03:00:00", + "2024-07-01 04:00:00", + "2024-07-01 05:00:00", + ], + "open": [100, 102, 101, 103, 104, 105], + "high": [102, 103, 102, 104, 105, 106], + "low": [99, 101, 100, 102, 103, 104], + "close": [101, 102, 103, 104, 105, 106], + "volume": [1000, 1500, 2000, 1800, 2400, 2500], + } + ), + ("XRP/USDT", "1d", CandleType.SPOT): pd.DataFrame( + { + "timestamp": [ + "2024-07-01 00:00:00", + "2024-07-01 01:00:00", + "2024-07-01 02:00:00", + "2024-07-01 03:00:00", + "2024-07-01 04:00:00", + "2024-07-01 05:00:00", + ], + "open": [100, 102, 101, 103, 104, 105], + "high": [102, 103, 102, 104, 105, 106], + "low": [99, 101, 100, 102, 103, 104], + "close": [101, 102, 103, 104, 105, 106], + "volume": [1000, 1500, 2000, 2500, 3000, 3500], + } + ), + } + + mocker.patch(f"{EXMS}.refresh_latest_ohlcv", MagicMock(return_value=mock_ohlcv_data)) + exchange = get_patched_exchange(mocker, rpl_config, exchange="binance") + pairlistmanager = PairListManager(exchange, rpl_config) + + remote_pairlist = PercentVolumeChangePairList( + exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0 + ) + + result = remote_pairlist.filter_pairlist(rpl_config["exchange"]["pair_whitelist"], {}) + + assert len(result) == 1 + assert result == ["ETH/USDT"] From b09f9e8c12fa638042336957181ccd732277881f Mon Sep 17 00:00:00 2001 From: jainanuj94 Date: Wed, 24 Jul 2024 19:12:11 +0530 Subject: [PATCH 017/242] 10348 | Update tests and add pairlist constants --- freqtrade/constants.py | 1 + tests/plugins/test_pairlist.py | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 86c1d71cd..4d8894c26 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -42,6 +42,7 @@ HYPEROPT_LOSS_BUILTIN = [ AVAILABLE_PAIRLISTS = [ "StaticPairList", "VolumePairList", + "PercentVolumeChangePairList", "ProducerPairList", "RemotePairList", "MarketCapPairList", diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 37ebdc58b..e5a19ea74 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -31,9 +31,11 @@ from tests.conftest import ( ) -# Exclude RemotePairList from tests. -# It has a mandatory parameter, and requires special handling, which happens in test_remotepairlist. -TESTABLE_PAIRLISTS = [p for p in AVAILABLE_PAIRLISTS if p not in ["RemotePairList"]] +# Exclude RemotePairList and PercentVolumeChangePairList from tests. +# They have mandatory parameters, and requires special handling, +# which happens in test_remotepairlist and test_percentvolumechangepairlist. +TESTABLE_PAIRLISTS = [p for p in AVAILABLE_PAIRLISTS + if p not in ["RemotePairList", "PercentVolumeChangePairList"]] @pytest.fixture(scope="function") From 1b81de01b48689cb805d20d88cea4b1d0b356901 Mon Sep 17 00:00:00 2001 From: jainanuj94 Date: Thu, 25 Jul 2024 00:04:06 +0530 Subject: [PATCH 018/242] 10348 | run ruff formatter --- freqtrade/plugins/pairlist/PercentVolumeChangePairList.py | 7 +++++-- tests/plugins/test_pairlist.py | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/freqtrade/plugins/pairlist/PercentVolumeChangePairList.py b/freqtrade/plugins/pairlist/PercentVolumeChangePairList.py index 0aee37959..777acda60 100644 --- a/freqtrade/plugins/pairlist/PercentVolumeChangePairList.py +++ b/freqtrade/plugins/pairlist/PercentVolumeChangePairList.py @@ -5,6 +5,7 @@ Provides dynamic pair list based on trade change sorted based on percentage change in volume over a defined period """ + import logging from datetime import timedelta from typing import Any, Dict, List, Literal @@ -118,8 +119,10 @@ class PercentVolumeChangePairList(IPairList): """ Short whitelist method description - used for startup-messages """ - return (f"{self.name} - top {self._pairlistconfig['number_assets']} percent " - f"volume change pairs.") + return ( + f"{self.name} - top {self._pairlistconfig['number_assets']} percent " + f"volume change pairs." + ) @staticmethod def description() -> str: diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index e5a19ea74..7e5fd4c12 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -34,8 +34,9 @@ from tests.conftest import ( # Exclude RemotePairList and PercentVolumeChangePairList from tests. # They have mandatory parameters, and requires special handling, # which happens in test_remotepairlist and test_percentvolumechangepairlist. -TESTABLE_PAIRLISTS = [p for p in AVAILABLE_PAIRLISTS - if p not in ["RemotePairList", "PercentVolumeChangePairList"]] +TESTABLE_PAIRLISTS = [ + p for p in AVAILABLE_PAIRLISTS if p not in ["RemotePairList", "PercentVolumeChangePairList"] +] @pytest.fixture(scope="function") From dad4f30597b693acab57075565d1d6455b535b82 Mon Sep 17 00:00:00 2001 From: jainanuj94 Date: Thu, 25 Jul 2024 23:33:28 +0530 Subject: [PATCH 019/242] Correct calculation for percent calculation and use tickers --- freqtrade/constants.py | 2 +- freqtrade/exchange/exchange.py | 1 + freqtrade/exchange/types.py | 1 + ...gePairList.py => PercentChangePairList.py} | 203 ++++++++++-------- tests/conftest.py | 2 +- tests/plugins/test_pairlist.py | 6 +- ...rlist.py => test_percentchangepairlist.py} | 133 ++++++------ 7 files changed, 195 insertions(+), 153 deletions(-) rename freqtrade/plugins/pairlist/{PercentVolumeChangePairList.py => PercentChangePairList.py} (64%) rename tests/plugins/{test_percentvolumechangepairlist.py => test_percentchangepairlist.py} (82%) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 4d8894c26..17a829955 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -42,7 +42,7 @@ HYPEROPT_LOSS_BUILTIN = [ AVAILABLE_PAIRLISTS = [ "StaticPairList", "VolumePairList", - "PercentVolumeChangePairList", + "PercentChangePairList", "ProducerPairList", "RemotePairList", "MarketCapPairList", diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 62f0ca4de..34ca8a3e8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -121,6 +121,7 @@ class Exchange: # Check https://github.com/ccxt/ccxt/issues/10767 for removal of ohlcv_volume_currency "ohlcv_volume_currency": "base", # "base" or "quote" "tickers_have_quoteVolume": True, + "tickers_have_percentage": True, "tickers_have_bid_ask": True, # bid / ask empty for fetch_tickers "tickers_have_price": True, "trades_pagination": "time", # Possible are "time" or "id" diff --git a/freqtrade/exchange/types.py b/freqtrade/exchange/types.py index 5568e4336..a0d315c78 100644 --- a/freqtrade/exchange/types.py +++ b/freqtrade/exchange/types.py @@ -12,6 +12,7 @@ class Ticker(TypedDict): last: Optional[float] quoteVolume: Optional[float] baseVolume: Optional[float] + percentage: Optional[float] # Several more - only listing required. diff --git a/freqtrade/plugins/pairlist/PercentVolumeChangePairList.py b/freqtrade/plugins/pairlist/PercentChangePairList.py similarity index 64% rename from freqtrade/plugins/pairlist/PercentVolumeChangePairList.py rename to freqtrade/plugins/pairlist/PercentChangePairList.py index 777acda60..a1be6729b 100644 --- a/freqtrade/plugins/pairlist/PercentVolumeChangePairList.py +++ b/freqtrade/plugins/pairlist/PercentChangePairList.py @@ -8,24 +8,24 @@ defined period import logging from datetime import timedelta -from typing import Any, Dict, List, Literal +from typing import Any, Dict, List, Literal, Optional from cachetools import TTLCache from freqtrade.constants import ListPairsWithTimeframes from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date -from freqtrade.exchange.types import Tickers +from freqtrade.exchange.types import Ticker, Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting from freqtrade.util import dt_now, format_ms_time logger = logging.getLogger(__name__) -SORT_VALUES = ["rolling_volume_change"] +SORT_VALUES = ["percentage"] -class PercentVolumeChangePairList(IPairList): +class PercentChangePairList(IPairList): is_pairlist_generator = True supports_backtesting = SupportsBacktesting.NO @@ -50,6 +50,7 @@ class PercentVolumeChangePairList(IPairList): self._lookback_days = self._pairlistconfig.get("lookback_days", 0) self._lookback_timeframe = self._pairlistconfig.get("lookback_timeframe", "1d") self._lookback_period = self._pairlistconfig.get("lookback_period", 0) + self._sort_direction: Optional[str] = self._pairlistconfig.get("sort_direction", "desc") self._def_candletype = self._config["candle_type_def"] if (self._lookback_days > 0) & (self._lookback_period > 0): @@ -80,11 +81,11 @@ class PercentVolumeChangePairList(IPairList): if not self._use_range and not ( self._exchange.exchange_has("fetchTickers") - and self._exchange.get_option("tickers_have_change") + and self._exchange.get_option("tickers_have_percentage") ): raise OperationalException( "Exchange does not support dynamic whitelist in this configuration. " - "Please edit your config and either remove PercentVolumeChangePairList, " + "Please edit your config and either remove PercentChangePairList, " "or switch to using candles. and restart the bot." ) @@ -94,9 +95,7 @@ class PercentVolumeChangePairList(IPairList): candle_limit = self._exchange.ohlcv_candle_limit( self._lookback_timeframe, self._config["candle_type_def"] ) - if self._lookback_period < 4: - raise OperationalException("ChangeFilter requires lookback_period to be >= 4") - self.log_once(f"Candle limit is {candle_limit}", logger.info) + if self._lookback_period > candle_limit: raise OperationalException( "ChangeFilter requires lookback_period to not " @@ -119,14 +118,11 @@ class PercentVolumeChangePairList(IPairList): """ Short whitelist method description - used for startup-messages """ - return ( - f"{self.name} - top {self._pairlistconfig['number_assets']} percent " - f"volume change pairs." - ) + return f"{self.name} - top {self._pairlistconfig['number_assets']} percent change pairs." @staticmethod def description() -> str: - return "Provides dynamic pair list based on percentage volume change." + return "Provides dynamic pair list based on percentage change." @staticmethod def available_parameters() -> Dict[str, PairlistParameter]: @@ -156,12 +152,14 @@ class PercentVolumeChangePairList(IPairList): "description": "Maximum value", "help": "Maximum value to use for filtering the pairlist.", }, - "refresh_period": { - "type": "number", - "default": 1800, - "description": "Refresh period", - "help": "Refresh period in seconds", + "sort_direction": { + "type": "option", + "default": "desc", + "options": ["", "asc", "desc"], + "description": "Sort pairlist", + "help": "Sort Pairlist ascending or descending by rate of change.", }, + **IPairList.refresh_period_parameter(), "lookback_days": { "type": "number", "default": 0, @@ -233,83 +231,24 @@ class PercentVolumeChangePairList(IPairList): :param tickers: Tickers (from exchange.get_tickers). May be cached. :return: new whitelist """ - self.log_once(f"Filter ticker is self use range {pairlist}", logger.warning) + filtered_tickers: List[Dict[str, Any]] = [{"symbol": k} for k in pairlist] if self._use_range: - filtered_tickers: List[Dict[str, Any]] = [{"symbol": k} for k in pairlist] - - # get lookback period in ms, for exchange ohlcv fetch - since_ms = ( - int( - timeframe_to_prev_date( - self._lookback_timeframe, - dt_now() - + timedelta( - minutes=-(self._lookback_period * self._tf_in_min) - self._tf_in_min - ), - ).timestamp() - ) - * 1000 - ) - - to_ms = ( - int( - timeframe_to_prev_date( - self._lookback_timeframe, dt_now() - timedelta(minutes=self._tf_in_min) - ).timestamp() - ) - * 1000 - ) - - # todo: utc date output for starting date - self.log_once( - f"Using change range of {self._lookback_period} candles, timeframe: " - f"{self._lookback_timeframe}, starting from {format_ms_time(since_ms)} " - f"till {format_ms_time(to_ms)}", - logger.info, - ) - needed_pairs: ListPairsWithTimeframes = [ - (p, self._lookback_timeframe, self._def_candletype) - for p in [s["symbol"] for s in filtered_tickers] - if p not in self._pair_cache - ] - - candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms) - - for i, p in enumerate(filtered_tickers): - pair_candles = ( - candles[(p["symbol"], self._lookback_timeframe, self._def_candletype)] - if (p["symbol"], self._lookback_timeframe, self._def_candletype) in candles - else None - ) - - # in case of candle data calculate typical price and change for candle - if pair_candles is not None and not pair_candles.empty: - pair_candles["rolling_volume_sum"] = ( - pair_candles["volume"].rolling(window=self._lookback_period).sum() - ) - pair_candles["rolling_volume_change"] = ( - pair_candles["rolling_volume_sum"].pct_change() * 100 - ) - - # ensure that a rolling sum over the lookback_period is built - # if pair_candles contains more candles than lookback_period - rolling_volume_change = pair_candles["rolling_volume_change"].fillna(0).iloc[-1] - - # replace change with a range change sum calculated above - filtered_tickers[i]["rolling_volume_change"] = rolling_volume_change - self.log_once(f"ticker {filtered_tickers[i]}", logger.info) - else: - filtered_tickers[i]["rolling_volume_change"] = 0 + # calculating using lookback_period + self.fetch_percent_change_from_lookback_period(filtered_tickers) else: - filtered_tickers = [v for k, v in tickers.items() if k in pairlist] + # Fetching 24h change by default from supported exchange tickers + self.fetch_percent_change_from_tickers(filtered_tickers, tickers) filtered_tickers = [v for v in filtered_tickers if v[self._sort_key] > self._min_value] if self._max_value is not None: filtered_tickers = [v for v in filtered_tickers if v[self._sort_key] < self._max_value] - sorted_tickers = sorted(filtered_tickers, reverse=True, key=lambda t: t[self._sort_key]) + sorted_tickers = sorted( + filtered_tickers, + reverse=self._sort_direction == "desc", + key=lambda t: t[self._sort_key], + ) - self.log_once(f"Sorted Tickers {sorted_tickers}", logger.info) # Validate whitelist to only have active market pairs pairs = self._whitelist_for_active_markets([s["symbol"] for s in sorted_tickers]) pairs = self.verify_blacklist(pairs, logmethod=logger.info) @@ -317,3 +256,91 @@ class PercentVolumeChangePairList(IPairList): pairs = pairs[: self._number_pairs] return pairs + + def fetch_candles_for_lookback_period(self, filtered_tickers): + since_ms = ( + int( + timeframe_to_prev_date( + self._lookback_timeframe, + dt_now() + + timedelta( + minutes=-(self._lookback_period * self._tf_in_min) - self._tf_in_min + ), + ).timestamp() + ) + * 1000 + ) + to_ms = ( + int( + timeframe_to_prev_date( + self._lookback_timeframe, dt_now() - timedelta(minutes=self._tf_in_min) + ).timestamp() + ) + * 1000 + ) + # todo: utc date output for starting date + self.log_once( + f"Using change range of {self._lookback_period} candles, timeframe: " + f"{self._lookback_timeframe}, starting from {format_ms_time(since_ms)} " + f"till {format_ms_time(to_ms)}", + logger.info, + ) + needed_pairs: ListPairsWithTimeframes = [ + (p, self._lookback_timeframe, self._def_candletype) + for p in [s["symbol"] for s in filtered_tickers] + if p not in self._pair_cache + ] + candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms) + return candles + + def fetch_percent_change_from_lookback_period(self, filtered_tickers): + # get lookback period in ms, for exchange ohlcv fetch + candles = self.fetch_candles_for_lookback_period(filtered_tickers) + + for i, p in enumerate(filtered_tickers): + pair_candles = ( + candles[(p["symbol"], self._lookback_timeframe, self._def_candletype)] + if (p["symbol"], self._lookback_timeframe, self._def_candletype) in candles + else None + ) + + # in case of candle data calculate typical price and change for candle + if pair_candles is not None and not pair_candles.empty: + current_close = pair_candles["close"].iloc[-1] + previous_close = pair_candles["close"].shift(self._lookback_period).iloc[-1] + pct_change = ( + ((current_close - previous_close) / previous_close) if previous_close > 0 else 0 + ) + + # replace change with a range change sum calculated above + filtered_tickers[i]["percentage"] = pct_change + self.log_once(f"Tickers: {filtered_tickers}", logger.info) + else: + filtered_tickers[i]["percentage"] = 0 + + def fetch_percent_change_from_tickers(self, filtered_tickers, tickers): + for i, p in enumerate(filtered_tickers): + # Filter out assets + if not self._validate_pair( + p["symbol"], tickers[p["symbol"]] if p["symbol"] in tickers else None + ): + filtered_tickers.remove(p) + else: + filtered_tickers[i]["percentage"] = tickers[p["symbol"]]["percentage"] + + def _validate_pair(self, pair: str, ticker: Optional[Ticker]) -> bool: + """ + Check if one price-step (pip) is > than a certain barrier. + :param pair: Pair that's currently validated + :param ticker: ticker dict as returned from ccxt.fetch_ticker + :return: True if the pair can stay, false if it should be removed + """ + if not ticker or "percentage" not in ticker or ticker["percentage"] is None: + self.log_once( + f"Removed {pair} from whitelist, because " + "ticker['percentage'] is empty (Usually no trade in the last 24h).", + logger.info, + ) + return False + + return True diff --git a/tests/conftest.py b/tests/conftest.py index fee8cab72..22bc2556e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2187,7 +2187,7 @@ def tickers(): "first": None, "last": 530.21, "change": 0.558, - "percentage": None, + "percentage": 2.349, "average": None, "baseVolume": 72300.0659, "quoteVolume": 37670097.3022171, diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 7e5fd4c12..31e746d5c 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -31,11 +31,11 @@ from tests.conftest import ( ) -# Exclude RemotePairList and PercentVolumeChangePairList from tests. +# Exclude RemotePairList and PercentVolumePairList from tests. # They have mandatory parameters, and requires special handling, -# which happens in test_remotepairlist and test_percentvolumechangepairlist. +# which happens in test_remotepairlist and test_percentchangepairlist. TESTABLE_PAIRLISTS = [ - p for p in AVAILABLE_PAIRLISTS if p not in ["RemotePairList", "PercentVolumeChangePairList"] + p for p in AVAILABLE_PAIRLISTS if p not in ["RemotePairList", "PercentChangePairList"] ] diff --git a/tests/plugins/test_percentvolumechangepairlist.py b/tests/plugins/test_percentchangepairlist.py similarity index 82% rename from tests/plugins/test_percentvolumechangepairlist.py rename to tests/plugins/test_percentchangepairlist.py index b09307151..0a7960d22 100644 --- a/tests/plugins/test_percentvolumechangepairlist.py +++ b/tests/plugins/test_percentchangepairlist.py @@ -7,7 +7,7 @@ import pytest from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.enums import CandleType from freqtrade.exceptions import OperationalException -from freqtrade.plugins.pairlist.PercentVolumeChangePairList import PercentVolumeChangePairList +from freqtrade.plugins.pairlist.PercentChangePairList import PercentChangePairList from freqtrade.plugins.pairlistmanager import PairListManager from tests.conftest import ( EXMS, @@ -33,9 +33,9 @@ def rpl_config(default_conf): def test_volume_change_pair_list_init_exchange_support(mocker, rpl_config): rpl_config["pairlists"] = [ { - "method": "PercentVolumeChangePairList", + "method": "PercentChangePairList", "number_assets": 2, - "sort_key": "rolling_volume_change", + "sort_key": "percentage", "min_value": 0, "refresh_period": 86400, } @@ -44,7 +44,7 @@ def test_volume_change_pair_list_init_exchange_support(mocker, rpl_config): with pytest.raises( OperationalException, match=r"Exchange does not support dynamic whitelist in this configuration. " - r"Please edit your config and either remove PercentVolumeChangePairList, " + r"Please edit your config and either remove PercentChangePairList, " r"or switch to using candles. and restart the bot.", ): get_patched_freqtradebot(mocker, rpl_config) @@ -53,9 +53,9 @@ def test_volume_change_pair_list_init_exchange_support(mocker, rpl_config): def test_volume_change_pair_list_init_wrong_refresh_period(mocker, rpl_config): rpl_config["pairlists"] = [ { - "method": "PercentVolumeChangePairList", + "method": "PercentChangePairList", "number_assets": 2, - "sort_key": "rolling_volume_change", + "sort_key": "percentage", "min_value": 0, "refresh_period": 1800, "lookback_days": 4, @@ -71,12 +71,31 @@ def test_volume_change_pair_list_init_wrong_refresh_period(mocker, rpl_config): get_patched_freqtradebot(mocker, rpl_config) +def test_volume_change_pair_list_init_invalid_sort_key(mocker, rpl_config): + rpl_config["pairlists"] = [ + { + "method": "PercentChangePairList", + "number_assets": 2, + "sort_key": "wrong_key", + "min_value": 0, + "refresh_period": 86400, + "lookback_days": 1, + } + ] + + with pytest.raises( + OperationalException, + match=r"key wrong_key not in \['percentage'\]", + ): + get_patched_freqtradebot(mocker, rpl_config) + + def test_volume_change_pair_list_init_wrong_lookback_period(mocker, rpl_config): rpl_config["pairlists"] = [ { - "method": "PercentVolumeChangePairList", + "method": "PercentChangePairList", "number_assets": 2, - "sort_key": "rolling_volume_change", + "sort_key": "percentage", "min_value": 0, "refresh_period": 86400, "lookback_days": 3, @@ -95,41 +114,9 @@ def test_volume_change_pair_list_init_wrong_lookback_period(mocker, rpl_config): rpl_config["pairlists"] = [ { - "method": "PercentVolumeChangePairList", + "method": "PercentChangePairList", "number_assets": 2, - "sort_key": "rolling_volume_change", - "min_value": 0, - "refresh_period": 86400, - "lookback_days": 3, - } - ] - - with pytest.raises( - OperationalException, match=r"ChangeFilter requires lookback_period to be >= 4" - ): - get_patched_freqtradebot(mocker, rpl_config) - - rpl_config["pairlists"] = [ - { - "method": "PercentVolumeChangePairList", - "number_assets": 2, - "sort_key": "rolling_volume_change", - "min_value": 0, - "refresh_period": 86400, - "lookback_period": 3, - } - ] - - with pytest.raises( - OperationalException, match=r"ChangeFilter requires lookback_period to be >= 4" - ): - get_patched_freqtradebot(mocker, rpl_config) - - rpl_config["pairlists"] = [ - { - "method": "PercentVolumeChangePairList", - "number_assets": 2, - "sort_key": "rolling_volume_change", + "sort_key": "percentage", "min_value": 0, "refresh_period": 86400, "lookback_days": 1001, @@ -147,8 +134,8 @@ def test_volume_change_pair_list_init_wrong_lookback_period(mocker, rpl_config): def test_volume_change_pair_list_init_wrong_config(mocker, rpl_config): rpl_config["pairlists"] = [ { - "method": "PercentVolumeChangePairList", - "sort_key": "rolling_volume_change", + "method": "PercentChangePairList", + "sort_key": "percentage", "min_value": 0, "refresh_period": 86400, } @@ -165,9 +152,9 @@ def test_volume_change_pair_list_init_wrong_config(mocker, rpl_config): def test_gen_pairlist_with_valid_change_pair_list_config(mocker, rpl_config, tickers, time_machine): rpl_config["pairlists"] = [ { - "method": "PercentVolumeChangePairList", + "method": "PercentChangePairList", "number_assets": 2, - "sort_key": "rolling_volume_change", + "sort_key": "percentage", "min_value": 0, "refresh_period": 86400, "lookback_days": 4, @@ -210,7 +197,7 @@ def test_gen_pairlist_with_valid_change_pair_list_config(mocker, rpl_config, tic ) ), ("TKN/USDT", "1d", CandleType.SPOT): pd.DataFrame( - # Make sure always have highest rolling_volume_change + # Make sure always have highest percentage { "timestamp": [ "2024-07-01 00:00:00", @@ -234,24 +221,25 @@ def test_gen_pairlist_with_valid_change_pair_list_config(mocker, rpl_config, tic exchange = get_patched_exchange(mocker, rpl_config, exchange="binance") pairlistmanager = PairListManager(exchange, rpl_config) - remote_pairlist = PercentVolumeChangePairList( + remote_pairlist = PercentChangePairList( exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0 ) result = remote_pairlist.gen_pairlist(tickers) assert len(result) == 2 - assert result == ["TKN/USDT", "BTC/USDT"] + assert result == ["NEO/USDT", "TKN/USDT"] def test_filter_pairlist_with_empty_ticker(mocker, rpl_config, tickers, time_machine): rpl_config["pairlists"] = [ { - "method": "PercentVolumeChangePairList", + "method": "PercentChangePairList", "number_assets": 2, - "sort_key": "rolling_volume_change", + "sort_key": "percentage", "min_value": 0, "refresh_period": 86400, + "sort_direction": "asc", "lookback_days": 4, } ] @@ -272,7 +260,7 @@ def test_filter_pairlist_with_empty_ticker(mocker, rpl_config, tickers, time_mac "open": [100, 102, 101, 103, 104, 105], "high": [102, 103, 102, 104, 105, 106], "low": [99, 101, 100, 102, 103, 104], - "close": [101, 102, 103, 104, 105, 106], + "close": [101, 102, 103, 104, 105, 105], "volume": [1000, 1500, 2000, 2500, 3000, 3500], } ), @@ -289,8 +277,8 @@ def test_filter_pairlist_with_empty_ticker(mocker, rpl_config, tickers, time_mac "open": [100, 102, 101, 103, 104, 105], "high": [102, 103, 102, 104, 105, 106], "low": [99, 101, 100, 102, 103, 104], - "close": [101, 102, 103, 104, 105, 106], - "volume": [1000, 1500, 2000, 2500, 3000, 3500], + "close": [101, 102, 103, 104, 105, 104], + "volume": [1000, 1500, 2000, 2500, 3000, 3400], } ), } @@ -299,22 +287,22 @@ def test_filter_pairlist_with_empty_ticker(mocker, rpl_config, tickers, time_mac exchange = get_patched_exchange(mocker, rpl_config, exchange="binance") pairlistmanager = PairListManager(exchange, rpl_config) - remote_pairlist = PercentVolumeChangePairList( + remote_pairlist = PercentChangePairList( exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0 ) result = remote_pairlist.filter_pairlist(rpl_config["exchange"]["pair_whitelist"], {}) assert len(result) == 2 - assert result == ["ETH/USDT", "XRP/USDT"] + assert result == ["XRP/USDT", "ETH/USDT"] def test_filter_pairlist_with_max_value_set(mocker, rpl_config, tickers, time_machine): rpl_config["pairlists"] = [ { - "method": "PercentVolumeChangePairList", + "method": "PercentChangePairList", "number_assets": 2, - "sort_key": "rolling_volume_change", + "sort_key": "percentage", "min_value": 0, "max_value": 15, "refresh_period": 86400, @@ -356,7 +344,7 @@ def test_filter_pairlist_with_max_value_set(mocker, rpl_config, tickers, time_ma "open": [100, 102, 101, 103, 104, 105], "high": [102, 103, 102, 104, 105, 106], "low": [99, 101, 100, 102, 103, 104], - "close": [101, 102, 103, 104, 105, 106], + "close": [101, 102, 103, 104, 105, 101], "volume": [1000, 1500, 2000, 2500, 3000, 3500], } ), @@ -366,7 +354,7 @@ def test_filter_pairlist_with_max_value_set(mocker, rpl_config, tickers, time_ma exchange = get_patched_exchange(mocker, rpl_config, exchange="binance") pairlistmanager = PairListManager(exchange, rpl_config) - remote_pairlist = PercentVolumeChangePairList( + remote_pairlist = PercentChangePairList( exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0 ) @@ -374,3 +362,28 @@ def test_filter_pairlist_with_max_value_set(mocker, rpl_config, tickers, time_ma assert len(result) == 1 assert result == ["ETH/USDT"] + + +def test_gen_pairlist_from_tickers(mocker, rpl_config, tickers): + rpl_config["pairlists"] = [ + { + "method": "PercentChangePairList", + "number_assets": 2, + "sort_key": "percentage", + "min_value": 0, + } + ] + + mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True)) + + exchange = get_patched_exchange(mocker, rpl_config, exchange="binance") + pairlistmanager = PairListManager(exchange, rpl_config) + + remote_pairlist = PercentChangePairList( + exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0 + ) + + result = remote_pairlist.gen_pairlist(tickers.return_value) + + assert len(result) == 1 + assert result == ["ETH/USDT"] From 4a768682ea8681e67a13e83bcb2389effcff11a8 Mon Sep 17 00:00:00 2001 From: jainanuj94 Date: Fri, 26 Jul 2024 13:13:26 +0530 Subject: [PATCH 020/242] Remove unnecessary logs and up description --- freqtrade/plugins/pairlist/PercentChangePairList.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/plugins/pairlist/PercentChangePairList.py b/freqtrade/plugins/pairlist/PercentChangePairList.py index a1be6729b..b9ffa7e45 100644 --- a/freqtrade/plugins/pairlist/PercentChangePairList.py +++ b/freqtrade/plugins/pairlist/PercentChangePairList.py @@ -1,9 +1,9 @@ """ -Change PairList provider +Percent Change PairList provider Provides dynamic pair list based on trade change -sorted based on percentage change in volume over a -defined period +sorted based on percentage change in price over a +defined period or as coming from ticker """ import logging @@ -314,7 +314,6 @@ class PercentChangePairList(IPairList): # replace change with a range change sum calculated above filtered_tickers[i]["percentage"] = pct_change - self.log_once(f"Tickers: {filtered_tickers}", logger.info) else: filtered_tickers[i]["percentage"] = 0 From 8637f4a70d835abd3fdf9f3beda7784d3ddc5fbe Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 Jul 2024 16:04:51 +0200 Subject: [PATCH 021/242] Remove SortKey dynamics and setting --- .../plugins/pairlist/PercentChangePairList.py | 28 ++++--------------- tests/plugins/test_percentchangepairlist.py | 19 ------------- 2 files changed, 5 insertions(+), 42 deletions(-) diff --git a/freqtrade/plugins/pairlist/PercentChangePairList.py b/freqtrade/plugins/pairlist/PercentChangePairList.py index b9ffa7e45..14b08b2b7 100644 --- a/freqtrade/plugins/pairlist/PercentChangePairList.py +++ b/freqtrade/plugins/pairlist/PercentChangePairList.py @@ -8,7 +8,7 @@ defined period or as coming from ticker import logging from datetime import timedelta -from typing import Any, Dict, List, Literal, Optional +from typing import Any, Dict, List, Optional from cachetools import TTLCache @@ -22,8 +22,6 @@ from freqtrade.util import dt_now, format_ms_time logger = logging.getLogger(__name__) -SORT_VALUES = ["percentage"] - class PercentChangePairList(IPairList): is_pairlist_generator = True @@ -40,9 +38,6 @@ class PercentChangePairList(IPairList): self._stake_currency = self._config["stake_currency"] self._number_pairs = self._pairlistconfig["number_assets"] - self._sort_key: Literal["rolling_volume_change"] = self._pairlistconfig.get( - "sort_key", "rolling_volume_change" - ) self._min_value = self._pairlistconfig.get("min_value", 0) self._max_value = self._pairlistconfig.get("max_value", None) self._refresh_period = self._pairlistconfig.get("refresh_period", 1800) @@ -89,9 +84,6 @@ class PercentChangePairList(IPairList): "or switch to using candles. and restart the bot." ) - if not self._validate_keys(self._sort_key): - raise OperationalException(f"key {self._sort_key} not in {SORT_VALUES}") - candle_limit = self._exchange.ohlcv_candle_limit( self._lookback_timeframe, self._config["candle_type_def"] ) @@ -111,9 +103,6 @@ class PercentChangePairList(IPairList): """ return not self._use_range - def _validate_keys(self, key): - return key in SORT_VALUES - def short_desc(self) -> str: """ Short whitelist method description - used for startup-messages @@ -133,13 +122,6 @@ class PercentChangePairList(IPairList): "description": "Number of assets", "help": "Number of assets to use from the pairlist", }, - "sort_key": { - "type": "option", - "default": "rolling_volume_change", - "options": SORT_VALUES, - "description": "Sort key", - "help": "Sort key to use for sorting the pairlist.", - }, "min_value": { "type": "number", "default": 0, @@ -210,7 +192,7 @@ class PercentChangePairList(IPairList): for k, v in tickers.items() if ( self._exchange.get_pair_quote_currency(k) == self._stake_currency - and (self._use_range or v.get(self._sort_key) is not None) + and (self._use_range or v.get("percentage") is not None) and v["symbol"] in _pairlist ) ] @@ -239,14 +221,14 @@ class PercentChangePairList(IPairList): # Fetching 24h change by default from supported exchange tickers self.fetch_percent_change_from_tickers(filtered_tickers, tickers) - filtered_tickers = [v for v in filtered_tickers if v[self._sort_key] > self._min_value] + filtered_tickers = [v for v in filtered_tickers if v["percentage"] > self._min_value] if self._max_value is not None: - filtered_tickers = [v for v in filtered_tickers if v[self._sort_key] < self._max_value] + filtered_tickers = [v for v in filtered_tickers if v["percentage"] < self._max_value] sorted_tickers = sorted( filtered_tickers, reverse=self._sort_direction == "desc", - key=lambda t: t[self._sort_key], + key=lambda t: t["percentage"], ) # Validate whitelist to only have active market pairs diff --git a/tests/plugins/test_percentchangepairlist.py b/tests/plugins/test_percentchangepairlist.py index 0a7960d22..df165cf98 100644 --- a/tests/plugins/test_percentchangepairlist.py +++ b/tests/plugins/test_percentchangepairlist.py @@ -71,25 +71,6 @@ def test_volume_change_pair_list_init_wrong_refresh_period(mocker, rpl_config): get_patched_freqtradebot(mocker, rpl_config) -def test_volume_change_pair_list_init_invalid_sort_key(mocker, rpl_config): - rpl_config["pairlists"] = [ - { - "method": "PercentChangePairList", - "number_assets": 2, - "sort_key": "wrong_key", - "min_value": 0, - "refresh_period": 86400, - "lookback_days": 1, - } - ] - - with pytest.raises( - OperationalException, - match=r"key wrong_key not in \['percentage'\]", - ): - get_patched_freqtradebot(mocker, rpl_config) - - def test_volume_change_pair_list_init_wrong_lookback_period(mocker, rpl_config): rpl_config["pairlists"] = [ { From 283e8045d82902bd5d7214b25ece714e9855d457 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 Jul 2024 16:05:59 +0200 Subject: [PATCH 022/242] PercentChangePairlist should partecipate in regular tests --- tests/plugins/test_pairlist.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 31e746d5c..37ebdc58b 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -31,12 +31,9 @@ from tests.conftest import ( ) -# Exclude RemotePairList and PercentVolumePairList from tests. -# They have mandatory parameters, and requires special handling, -# which happens in test_remotepairlist and test_percentchangepairlist. -TESTABLE_PAIRLISTS = [ - p for p in AVAILABLE_PAIRLISTS if p not in ["RemotePairList", "PercentChangePairList"] -] +# Exclude RemotePairList from tests. +# It has a mandatory parameter, and requires special handling, which happens in test_remotepairlist. +TESTABLE_PAIRLISTS = [p for p in AVAILABLE_PAIRLISTS if p not in ["RemotePairList"]] @pytest.fixture(scope="function") From 4ac7a4fdab27c7ad341e77b1d4dc50da22fd4f8d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 Jul 2024 16:07:51 +0200 Subject: [PATCH 023/242] Allow empty min_Value setting... --- freqtrade/plugins/pairlist/PercentChangePairList.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/plugins/pairlist/PercentChangePairList.py b/freqtrade/plugins/pairlist/PercentChangePairList.py index 14b08b2b7..457c29275 100644 --- a/freqtrade/plugins/pairlist/PercentChangePairList.py +++ b/freqtrade/plugins/pairlist/PercentChangePairList.py @@ -38,7 +38,7 @@ class PercentChangePairList(IPairList): self._stake_currency = self._config["stake_currency"] self._number_pairs = self._pairlistconfig["number_assets"] - self._min_value = self._pairlistconfig.get("min_value", 0) + self._min_value = self._pairlistconfig.get("min_value", None) self._max_value = self._pairlistconfig.get("max_value", None) self._refresh_period = self._pairlistconfig.get("refresh_period", 1800) self._pair_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) @@ -221,7 +221,8 @@ class PercentChangePairList(IPairList): # Fetching 24h change by default from supported exchange tickers self.fetch_percent_change_from_tickers(filtered_tickers, tickers) - filtered_tickers = [v for v in filtered_tickers if v["percentage"] > self._min_value] + if self._min_value is not None: + filtered_tickers = [v for v in filtered_tickers if v["percentage"] > self._min_value] if self._max_value is not None: filtered_tickers = [v for v in filtered_tickers if v["percentage"] < self._max_value] From 206baf7d80c49382b37355cf7691f1003c7c98b3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 Jul 2024 16:13:17 +0200 Subject: [PATCH 024/242] chore: add a bit of typehinting --- freqtrade/plugins/pairlist/PercentChangePairList.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/freqtrade/plugins/pairlist/PercentChangePairList.py b/freqtrade/plugins/pairlist/PercentChangePairList.py index 457c29275..eded33d1d 100644 --- a/freqtrade/plugins/pairlist/PercentChangePairList.py +++ b/freqtrade/plugins/pairlist/PercentChangePairList.py @@ -11,8 +11,9 @@ from datetime import timedelta from typing import Any, Dict, List, Optional from cachetools import TTLCache +from pandas import DataFrame -from freqtrade.constants import ListPairsWithTimeframes +from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date from freqtrade.exchange.types import Ticker, Tickers @@ -168,8 +169,6 @@ class PercentChangePairList(IPairList): :param tickers: Tickers (from exchange.get_tickers). May be cached. :return: List of pairs """ - # Generate dynamic whitelist - # Must always run if this pairlist is not the first in the list. pairlist = self._pair_cache.get("pairlist") if pairlist: # Item found - no refresh necessary @@ -240,7 +239,9 @@ class PercentChangePairList(IPairList): return pairs - def fetch_candles_for_lookback_period(self, filtered_tickers): + def fetch_candles_for_lookback_period( + self, filtered_tickers: List[Dict[str, str]] + ) -> Dict[PairWithTimeframe, DataFrame]: since_ms = ( int( timeframe_to_prev_date( @@ -276,7 +277,7 @@ class PercentChangePairList(IPairList): candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms) return candles - def fetch_percent_change_from_lookback_period(self, filtered_tickers): + def fetch_percent_change_from_lookback_period(self, filtered_tickers: List[Dict[str, Any]]): # get lookback period in ms, for exchange ohlcv fetch candles = self.fetch_candles_for_lookback_period(filtered_tickers) @@ -300,7 +301,7 @@ class PercentChangePairList(IPairList): else: filtered_tickers[i]["percentage"] = 0 - def fetch_percent_change_from_tickers(self, filtered_tickers, tickers): + def fetch_percent_change_from_tickers(self, filtered_tickers: List[Dict[str, Any]], tickers): for i, p in enumerate(filtered_tickers): # Filter out assets if not self._validate_pair( From 4932473b3f49c7d3d2e1258d504a66d765ca1a8f Mon Sep 17 00:00:00 2001 From: jainanuj94 Date: Sat, 27 Jul 2024 23:41:32 +0530 Subject: [PATCH 025/242] Add documentation --- docs/includes/pairlists.md | 81 +++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index fbf8f4be0..15f1dbc85 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -2,11 +2,11 @@ Pairlist Handlers define the list of pairs (pairlist) that the bot should trade. They are configured in the `pairlists` section of the configuration settings. -In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) Pairlist Handler). +In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) and [`PercentChangePairList`](#percent-change-pair-list) Pairlist Handlers). Additionally, [`AgeFilter`](#agefilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter), [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist. -If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You can define either `StaticPairList`, `VolumePairList`, `ProducerPairList`, `RemotePairList` or `MarketCapPairList` as the starting Pairlist Handler. +If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You can define either `StaticPairList`, `VolumePairList`, `ProducerPairList`, `RemotePairList`, `MarketCapPairList` or `PercentChangePairList` as the starting Pairlist Handler. Inactive markets are always removed from the resulting pairlist. Explicitly blacklisted pairs (those in the `pair_blacklist` configuration setting) are also always removed from the resulting pairlist. @@ -22,6 +22,7 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged * [`StaticPairList`](#static-pair-list) (default, if not configured differently) * [`VolumePairList`](#volume-pair-list) +* [`PercentChangePairList`](#percent-change-pair-list) * [`ProducerPairList`](#producerpairlist) * [`RemotePairList`](#remotepairlist) * [`MarketCapPairList`](#marketcappairlist) @@ -152,6 +153,82 @@ More sophisticated approach can be used, by using `lookback_timeframe` for candl !!! Note `VolumePairList` does not support backtesting mode. +#### Percent Change Pair List + +`PercentChangePairList` filters and sorts pairs based on the percentage change in their price over the last 24 hours or any defined timeframe as part of advanced options. This allows traders to focus on assets that have experienced significant price movements, either positive or negative. + +**Configuration Options** + +- `number_assets`: Specifies the number of top pairs to select based on the 24-hour percentage change. +- `min_value`: Sets a minimum percentage change threshold. Pairs with a percentage change below this value will be filtered out. +- `max_value`: Sets a maximum percentage change threshold. Pairs with a percentage change above this value will be filtered out. +- `refresh_period`: Defines the interval (in seconds) at which the pairlist will be refreshed. The default is 1800 seconds (30 minutes). +- `lookback_days`: Number of days to look back. When `lookback_days` is selected, the `lookback_timeframe` is defaulted to 1 day. +- `lookback_timeframe`: Timeframe to use for the lookback period. +- `lookback_period`: Number of periods to look back at. + +When PercentChangePairList is used after other Pairlist Handlers, it will operate on the outputs of those handlers. If it is the leading Pairlist Handler, it will select pairs from all available markets with the specified stake currency. + +`PercentChangePairList` uses ticker data from the exchange, provided via the ccxt library: +The percentage change is calculated as the change in price over the last 24 hours, expressed as a percentage, depending on the exchange. + +??? Tip "Unsupported exchanges" + On some exchanges (like HTX), regular PercentChangePairList does not work as the api does not natively provide 24h percent change in price. This can be worked around by using candle data to calculate the percentage change. To roughly simulate 24h percent change, you can use the following configuration. Please note that These pairlists will only refresh once per day. + ```json + "pairlists": [ + { + "method": "PercentChangePairList", + "number_assets": 20, + "sort_key": "percentage", + "min_value": 0, + "refresh_period": 86400, + "lookback_days": 1 + } + ], + ``` + +**Example Configuration to Read from Ticker** +```json +"pairlists": [ + { + "method": "PercentChangePairList", + "number_assets": 15, + "sort_key": "percentage", + "min_value": -10, + "max_value": 50 + } +], +``` +In this configuration: + +1. The top 15 pairs are selected based on the highest percentage change in price over the last 24 hours. +2. Only pairs with a percentage change between -10% and 50% are considered. + +**Example Configuration to Read from Candles** +```json +"pairlists": [ + { + "method": "PercentChangePairList", + "number_assets": 15, + "sort_key": "percentage", + "min_value": 0, + "refresh_period": 3600, + "lookback_timeframe": "1h", + "lookback_period": 72 + } +], +``` +This example builds the percent change pairs based on a rolling period of 3 days of 1-hour candles by using `lookback_timeframe` for candle size and `lookback_period` which specifies the number of candles. + +!!! Warning "Range look back and refresh period" + When used in conjunction with `lookback_days` and `lookback_timeframe` the `refresh_period` can not be smaller than the candle size in seconds. As this will result in unnecessary requests to the exchanges API. + +!!! Warning "Performance implications when using lookback range" + If used in first position in combination with lookback, the computation of the range-based percent change can be time and resource consuming, as it downloads candles for all tradable pairs. Hence it's highly advised to use the standard approach with `PercentChangePairList` to narrow the pairlist down for further range volume calculation. + +!!! Note Backtesting + `PercentChangePairList` does not support backtesting mode. + #### ProducerPairList With `ProducerPairList`, you can reuse the pairlist from a [Producer](producer-consumer.md) without explicitly defining the pairlist on each consumer. From ac1e405c341c0eb5e1ffd470e8f8a3dce6f348fa Mon Sep 17 00:00:00 2001 From: jainanuj94 Date: Sun, 28 Jul 2024 21:10:20 +0530 Subject: [PATCH 026/242] Update documentation and fix doc test --- docs/includes/pairlists.md | 13 ++++++++----- freqtrade/plugins/pairlist/PercentChangePairList.py | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 15f1dbc85..9ea797682 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -162,6 +162,7 @@ More sophisticated approach can be used, by using `lookback_timeframe` for candl - `number_assets`: Specifies the number of top pairs to select based on the 24-hour percentage change. - `min_value`: Sets a minimum percentage change threshold. Pairs with a percentage change below this value will be filtered out. - `max_value`: Sets a maximum percentage change threshold. Pairs with a percentage change above this value will be filtered out. +- `sort_direction`: Specifies the order in which pairs are sorted based on their percentage change. Accepts two values: `asc` for ascending order and `desc` for descending order. - `refresh_period`: Defines the interval (in seconds) at which the pairlist will be refreshed. The default is 1800 seconds (30 minutes). - `lookback_days`: Number of days to look back. When `lookback_days` is selected, the `lookback_timeframe` is defaulted to 1 day. - `lookback_timeframe`: Timeframe to use for the lookback period. @@ -170,7 +171,7 @@ More sophisticated approach can be used, by using `lookback_timeframe` for candl When PercentChangePairList is used after other Pairlist Handlers, it will operate on the outputs of those handlers. If it is the leading Pairlist Handler, it will select pairs from all available markets with the specified stake currency. `PercentChangePairList` uses ticker data from the exchange, provided via the ccxt library: -The percentage change is calculated as the change in price over the last 24 hours, expressed as a percentage, depending on the exchange. +The percentage change is calculated as the change in price over the last 24 hours. ??? Tip "Unsupported exchanges" On some exchanges (like HTX), regular PercentChangePairList does not work as the api does not natively provide 24h percent change in price. This can be worked around by using candle data to calculate the percentage change. To roughly simulate 24h percent change, you can use the following configuration. Please note that These pairlists will only refresh once per day. @@ -179,7 +180,6 @@ The percentage change is calculated as the change in price over the last 24 hour { "method": "PercentChangePairList", "number_assets": 20, - "sort_key": "percentage", "min_value": 0, "refresh_period": 86400, "lookback_days": 1 @@ -193,7 +193,6 @@ The percentage change is calculated as the change in price over the last 24 hour { "method": "PercentChangePairList", "number_assets": 15, - "sort_key": "percentage", "min_value": -10, "max_value": 50 } @@ -220,13 +219,17 @@ In this configuration: ``` This example builds the percent change pairs based on a rolling period of 3 days of 1-hour candles by using `lookback_timeframe` for candle size and `lookback_period` which specifies the number of candles. +The percent change in price is calculated using the following formula, which expresses the percentage difference between the current candle's close price and the previous candle's close price, as defined by the specified timeframe and lookback period: + +$$ Percent Change = (\frac{Current Close - Previous Close}{Previous Close}) * 100 $$ + !!! Warning "Range look back and refresh period" When used in conjunction with `lookback_days` and `lookback_timeframe` the `refresh_period` can not be smaller than the candle size in seconds. As this will result in unnecessary requests to the exchanges API. !!! Warning "Performance implications when using lookback range" - If used in first position in combination with lookback, the computation of the range-based percent change can be time and resource consuming, as it downloads candles for all tradable pairs. Hence it's highly advised to use the standard approach with `PercentChangePairList` to narrow the pairlist down for further range volume calculation. + If used in first position in combination with lookback, the computation of the range-based percent change can be time and resource consuming, as it downloads candles for all tradable pairs. Hence it's highly advised to use the standard approach with `PercentChangePairList` to narrow the pairlist down for further percent-change calculation. -!!! Note Backtesting +!!! Note "Backtesting" `PercentChangePairList` does not support backtesting mode. #### ProducerPairList diff --git a/freqtrade/plugins/pairlist/PercentChangePairList.py b/freqtrade/plugins/pairlist/PercentChangePairList.py index eded33d1d..b22891b98 100644 --- a/freqtrade/plugins/pairlist/PercentChangePairList.py +++ b/freqtrade/plugins/pairlist/PercentChangePairList.py @@ -125,7 +125,7 @@ class PercentChangePairList(IPairList): }, "min_value": { "type": "number", - "default": 0, + "default": None, "description": "Minimum value", "help": "Minimum value to use for filtering the pairlist.", }, From 27aed5cd7ee907cb8a990ee7324d28646994297f Mon Sep 17 00:00:00 2001 From: jainanuj94 Date: Sun, 28 Jul 2024 22:34:34 +0530 Subject: [PATCH 027/242] Update schema.json --- build_helpers/schema.json | 1 + 1 file changed, 1 insertion(+) diff --git a/build_helpers/schema.json b/build_helpers/schema.json index c0933d9f8..c2b930558 100644 --- a/build_helpers/schema.json +++ b/build_helpers/schema.json @@ -562,6 +562,7 @@ "enum": [ "StaticPairList", "VolumePairList", + "PercentChangePairList", "ProducerPairList", "RemotePairList", "MarketCapPairList", From faaa1050da244e2ad69701d452f5aa62fcd7de0e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 28 Jul 2024 20:10:30 +0200 Subject: [PATCH 028/242] chore: Bump dev version to 2024.8 --- freqtrade/__init__.py | 2 +- ft_client/freqtrade_client/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 0cb8dd961..be9980671 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,6 +1,6 @@ """Freqtrade bot""" -__version__ = "2024.7-dev" +__version__ = "2024.8-dev" if "dev" in __version__: from pathlib import Path diff --git a/ft_client/freqtrade_client/__init__.py b/ft_client/freqtrade_client/__init__.py index 9311fc85d..68ef44422 100644 --- a/ft_client/freqtrade_client/__init__.py +++ b/ft_client/freqtrade_client/__init__.py @@ -1,7 +1,7 @@ from freqtrade_client.ft_rest_client import FtRestClient -__version__ = "2024.7-dev" +__version__ = "2024.8-dev" if "dev" in __version__: from pathlib import Path From 719889b27a737629cca33ea1f19d3e379415174d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 03:05:45 +0000 Subject: [PATCH 029/242] chore(deps-dev): bump pytest from 8.3.1 to 8.3.2 in the pytest group Bumps the pytest group with 1 update: [pytest](https://github.com/pytest-dev/pytest). Updates `pytest` from 8.3.1 to 8.3.2 - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.3.1...8.3.2) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch dependency-group: pytest ... 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 a89604996..3557a7c68 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,7 +10,7 @@ coveralls==4.0.1 ruff==0.5.4 mypy==1.11.0 pre-commit==3.7.1 -pytest==8.3.1 +pytest==8.3.2 pytest-asyncio==0.23.8 pytest-cov==5.0.0 pytest-mock==3.14.0 From fd2be958ba721256b3ed4c5d7c2d986a75929487 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 03:06:12 +0000 Subject: [PATCH 030/242] chore(deps): bump mkdocs-material in the mkdocs group Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material). Updates `mkdocs-material` from 9.5.29 to 9.5.30 - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.29...9.5.30) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch dependency-group: mkdocs ... 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 20beace35..c833d2853 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.6 mkdocs==1.6.0 -mkdocs-material==9.5.29 +mkdocs-material==9.5.30 mdx_truly_sane_lists==1.3 pymdown-extensions==10.8.1 jinja2==3.1.4 From 9fd6d7318ef83de29f99bd21dbedf412b62f4b38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 03:07:22 +0000 Subject: [PATCH 031/242] chore(deps): bump ccxt from 4.3.65 to 4.3.68 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.65 to 4.3.68. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/4.3.65...4.3.68) --- 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 570459975..a15ac4f30 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ bottleneck==1.4.0 numexpr==2.10.1 pandas-ta==0.3.14b -ccxt==4.3.65 +ccxt==4.3.68 cryptography==43.0.0 aiohttp==3.9.5 SQLAlchemy==2.0.31 From 2f4e4343c2b56f23158ead6757958f27880df058 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 03:07:44 +0000 Subject: [PATCH 032/242] chore(deps): bump torch from 2.2.2 to 2.4.0 Bumps [torch](https://github.com/pytorch/pytorch) from 2.2.2 to 2.4.0. - [Release notes](https://github.com/pytorch/pytorch/releases) - [Changelog](https://github.com/pytorch/pytorch/blob/main/RELEASE.md) - [Commits](https://github.com/pytorch/pytorch/compare/v2.2.2...v2.4.0) --- updated-dependencies: - dependency-name: torch dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-freqai-rl.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt index c278716fb..740de2559 100644 --- a/requirements-freqai-rl.txt +++ b/requirements-freqai-rl.txt @@ -3,7 +3,7 @@ # Required for freqai-rl torch==2.3.1; sys_platform != 'darwin' or platform_machine != 'x86_64' -torch==2.2.2; sys_platform == 'darwin' and platform_machine == 'x86_64' +torch==2.4.0; sys_platform == 'darwin' and platform_machine == 'x86_64' gymnasium==0.29.1 stable_baselines3==2.3.2 sb3_contrib>=2.2.1 From baeced32c32095b8ee0dc4cd077fe6f2cc9f968f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 03:08:10 +0000 Subject: [PATCH 033/242] chore(deps): bump python-rapidjson from 1.18 to 1.19 Bumps [python-rapidjson](https://github.com/python-rapidjson/python-rapidjson) from 1.18 to 1.19. - [Changelog](https://github.com/python-rapidjson/python-rapidjson/blob/master/CHANGES.rst) - [Commits](https://github.com/python-rapidjson/python-rapidjson/compare/v1.18...v1.19) --- updated-dependencies: - dependency-name: python-rapidjson dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- ft_client/requirements.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ft_client/requirements.txt b/ft_client/requirements.txt index 5e6856e92..c0d627247 100644 --- a/ft_client/requirements.txt +++ b/ft_client/requirements.txt @@ -1,3 +1,3 @@ # Requirements for freqtrade client library requests==2.32.3 -python-rapidjson==1.18 +python-rapidjson==1.19 diff --git a/requirements.txt b/requirements.txt index 570459975..261f3c473 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,7 @@ pyarrow==17.0.0; platform_machine != 'armv7l' py_find_1st==1.1.6 # Load ticker files 30% faster -python-rapidjson==1.18 +python-rapidjson==1.19 # Properly format api responses orjson==3.10.6 From 097786c62da98f66bad90fbb253d89804c1ce9c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 03:08:18 +0000 Subject: [PATCH 034/242] chore(deps): bump plotly from 5.22.0 to 5.23.0 Bumps [plotly](https://github.com/plotly/plotly.py) from 5.22.0 to 5.23.0. - [Release notes](https://github.com/plotly/plotly.py/releases) - [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md) - [Commits](https://github.com/plotly/plotly.py/compare/v5.22.0...v5.23.0) --- updated-dependencies: - dependency-name: plotly dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index b4dc2e46c..6641fe524 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,4 +1,4 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==5.22.0 +plotly==5.23.0 From 5e852ebb5d1654fde5bf7b9aa6c02b9a45cc931c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 03:08:24 +0000 Subject: [PATCH 035/242] chore(deps): bump lightgbm from 4.4.0 to 4.5.0 Bumps [lightgbm](https://github.com/microsoft/LightGBM) from 4.4.0 to 4.5.0. - [Release notes](https://github.com/microsoft/LightGBM/releases) - [Commits](https://github.com/microsoft/LightGBM/compare/v4.4.0...v4.5.0) --- updated-dependencies: - dependency-name: lightgbm 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 c57e66d2d..f2d6bd5f5 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -6,7 +6,7 @@ scikit-learn==1.5.1 joblib==1.4.2 catboost==1.2.5; 'arm' not in platform_machine -lightgbm==4.4.0 +lightgbm==4.5.0 xgboost==2.0.3 tensorboard==2.17.0 datasieve==0.1.7 From 5e1038dc673910dcb7c33d32fa99e023921c57ce Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 29 Jul 2024 07:00:17 +0200 Subject: [PATCH 036/242] chore: Fix torch version bump --- requirements-freqai-rl.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt index 740de2559..aa4ae5699 100644 --- a/requirements-freqai-rl.txt +++ b/requirements-freqai-rl.txt @@ -2,8 +2,8 @@ -r requirements-freqai.txt # Required for freqai-rl -torch==2.3.1; sys_platform != 'darwin' or platform_machine != 'x86_64' -torch==2.4.0; sys_platform == 'darwin' and platform_machine == 'x86_64' +torch==2.2.2; sys_platform == 'darwin' and platform_machine == 'x86_64' +torch==2.4.0; sys_platform != 'darwin' or platform_machine != 'x86_64' gymnasium==0.29.1 stable_baselines3==2.3.2 sb3_contrib>=2.2.1 From a1490d07b4326ac4896568bc1d1a47142f0170ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 05:32:28 +0000 Subject: [PATCH 037/242] chore(deps-dev): bump ruff from 0.5.4 to 0.5.5 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.4 to 0.5.5. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.5.4...0.5.5) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 3557a7c68..2b605e8ce 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==4.0.1 -ruff==0.5.4 +ruff==0.5.5 mypy==1.11.0 pre-commit==3.7.1 pytest==8.3.2 From 3789e1339bb93efc2c8ba99977b9aedb34008855 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 05:32:28 +0000 Subject: [PATCH 038/242] chore(deps): bump pymdown-extensions from 10.8.1 to 10.9 Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.8.1 to 10.9. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.8.1...10.9) --- 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 c833d2853..47fcdaa32 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -2,5 +2,5 @@ markdown==3.6 mkdocs==1.6.0 mkdocs-material==9.5.30 mdx_truly_sane_lists==1.3 -pymdown-extensions==10.8.1 +pymdown-extensions==10.9 jinja2==3.1.4 From c8b75808302733996978a7c2ce936aca2b3e74a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 08:27:01 +0000 Subject: [PATCH 039/242] chore(deps-dev): bump pre-commit from 3.7.1 to 3.8.0 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.7.1 to 3.8.0. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v3.7.1...v3.8.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 2b605e8ce..554cf7778 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,7 +9,7 @@ coveralls==4.0.1 ruff==0.5.5 mypy==1.11.0 -pre-commit==3.7.1 +pre-commit==3.8.0 pytest==8.3.2 pytest-asyncio==0.23.8 pytest-cov==5.0.0 From 1ebbfffd2ab463eda74f6bd4a36241194c84390d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 29 Jul 2024 19:42:20 +0200 Subject: [PATCH 040/242] chore: hyperliquid doesn't have historic ohlcv --- freqtrade/exchange/hyperliquid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index d2b1e1482..9b8598432 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -17,7 +17,7 @@ class Hyperliquid(Exchange): _ft_has: Dict = { # Only the most recent 5000 candles are available according to the # exchange's API documentation. - "ohlcv_has_history": True, + "ohlcv_has_history": False, "ohlcv_candle_limit": 5000, "trades_has_history": False, # Trades endpoint doesn't seem available. "exchange_has_overrides": {"fetchTrades": False}, From 40b20c5595cbbad5276a9c59412019766b19e35e Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Tue, 30 Jul 2024 03:02:51 +0000 Subject: [PATCH 041/242] chore: update pre-commit hooks --- .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 c78811f30..58c43d454 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.5.4' + rev: 'v0.5.5' hooks: - id: ruff From eb0fc0fc807ffae7c891c4e823a166c8726bd1fa Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Jul 2024 20:29:21 +0200 Subject: [PATCH 042/242] docs: Fix minor typo --- docs/includes/pairlists.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 9ea797682..b3b69f996 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -159,22 +159,22 @@ More sophisticated approach can be used, by using `lookback_timeframe` for candl **Configuration Options** -- `number_assets`: Specifies the number of top pairs to select based on the 24-hour percentage change. -- `min_value`: Sets a minimum percentage change threshold. Pairs with a percentage change below this value will be filtered out. -- `max_value`: Sets a maximum percentage change threshold. Pairs with a percentage change above this value will be filtered out. -- `sort_direction`: Specifies the order in which pairs are sorted based on their percentage change. Accepts two values: `asc` for ascending order and `desc` for descending order. -- `refresh_period`: Defines the interval (in seconds) at which the pairlist will be refreshed. The default is 1800 seconds (30 minutes). -- `lookback_days`: Number of days to look back. When `lookback_days` is selected, the `lookback_timeframe` is defaulted to 1 day. -- `lookback_timeframe`: Timeframe to use for the lookback period. -- `lookback_period`: Number of periods to look back at. +* `number_assets`: Specifies the number of top pairs to select based on the 24-hour percentage change. +* `min_value`: Sets a minimum percentage change threshold. Pairs with a percentage change below this value will be filtered out. +* `max_value`: Sets a maximum percentage change threshold. Pairs with a percentage change above this value will be filtered out. +* `sort_direction`: Specifies the order in which pairs are sorted based on their percentage change. Accepts two values: `asc` for ascending order and `desc` for descending order. +* `refresh_period`: Defines the interval (in seconds) at which the pairlist will be refreshed. The default is 1800 seconds (30 minutes). +* `lookback_days`: Number of days to look back. When `lookback_days` is selected, the `lookback_timeframe` is defaulted to 1 day. +* `lookback_timeframe`: Timeframe to use for the lookback period. +* `lookback_period`: Number of periods to look back at. When PercentChangePairList is used after other Pairlist Handlers, it will operate on the outputs of those handlers. If it is the leading Pairlist Handler, it will select pairs from all available markets with the specified stake currency. `PercentChangePairList` uses ticker data from the exchange, provided via the ccxt library: The percentage change is calculated as the change in price over the last 24 hours. -??? Tip "Unsupported exchanges" - On some exchanges (like HTX), regular PercentChangePairList does not work as the api does not natively provide 24h percent change in price. This can be worked around by using candle data to calculate the percentage change. To roughly simulate 24h percent change, you can use the following configuration. Please note that These pairlists will only refresh once per day. +??? Note "Unsupported exchanges" + On some exchanges (like HTX), regular PercentChangePairList does not work as the api does not natively provide 24h percent change in price. This can be worked around by using candle data to calculate the percentage change. To roughly simulate 24h percent change, you can use the following configuration. Please note that these pairlists will only refresh once per day. ```json "pairlists": [ { @@ -188,6 +188,7 @@ The percentage change is calculated as the change in price over the last 24 hour ``` **Example Configuration to Read from Ticker** + ```json "pairlists": [ { @@ -198,12 +199,14 @@ The percentage change is calculated as the change in price over the last 24 hour } ], ``` + In this configuration: 1. The top 15 pairs are selected based on the highest percentage change in price over the last 24 hours. 2. Only pairs with a percentage change between -10% and 50% are considered. **Example Configuration to Read from Candles** + ```json "pairlists": [ { @@ -217,6 +220,7 @@ In this configuration: } ], ``` + This example builds the percent change pairs based on a rolling period of 3 days of 1-hour candles by using `lookback_timeframe` for candle size and `lookback_period` which specifies the number of candles. The percent change in price is calculated using the following formula, which expresses the percentage difference between the current candle's close price and the previous candle's close price, as defined by the specified timeframe and lookback period: From c40ac27d71e57ea2026ad29d710427776c811cb5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 31 Jul 2024 20:36:44 +0200 Subject: [PATCH 043/242] chore: Remove pip pin from ci --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e4ef2faa..bb3ea2221 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: - name: Installation - *nix run: | - python -m pip install --upgrade "pip<=24.0" wheel + python -m pip install --upgrade pip wheel export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH export TA_LIBRARY_PATH=${HOME}/dependencies/lib export TA_INCLUDE_PATH=${HOME}/dependencies/include @@ -197,7 +197,7 @@ jobs: - name: Installation (python) run: | - python -m pip install --upgrade "pip<=24.0" wheel + python -m pip install --upgrade pip wheel export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH export TA_LIBRARY_PATH=${HOME}/dependencies/lib export TA_INCLUDE_PATH=${HOME}/dependencies/include @@ -427,7 +427,7 @@ jobs: - name: Installation - *nix run: | - python -m pip install --upgrade "pip<=24.0" wheel + python -m pip install --upgrade pip wheel export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH export TA_LIBRARY_PATH=${HOME}/dependencies/lib export TA_INCLUDE_PATH=${HOME}/dependencies/include From 8105f51603692404d56f0eefcb2b54a2b618e33b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 31 Jul 2024 20:39:12 +0200 Subject: [PATCH 044/242] chore: remove pip lock from Dockerfiles --- Dockerfile | 2 +- docker/Dockerfile.armhf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index cedeafbe6..e435f7f1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ FROM base as python-deps RUN apt-get update \ && apt-get -y install build-essential libssl-dev git libffi-dev libgfortran5 pkg-config cmake gcc \ && apt-get clean \ - && pip install --upgrade "pip<=24.0" wheel + && pip install --upgrade pip wheel # Install TA-lib COPY build_helpers/* /tmp/ diff --git a/docker/Dockerfile.armhf b/docker/Dockerfile.armhf index fbd952111..688254122 100644 --- a/docker/Dockerfile.armhf +++ b/docker/Dockerfile.armhf @@ -17,7 +17,7 @@ RUN mkdir /freqtrade \ && chown ftuser:ftuser /freqtrade \ # Allow sudoers && echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers \ - && pip install --upgrade "pip<=24.0" + && pip install --upgrade pip WORKDIR /freqtrade From 02621eee74217c38c4bf34adacbd14311903f3d1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 31 Jul 2024 20:39:21 +0200 Subject: [PATCH 045/242] chore: remove pip version lock from instal scripts --- build_helpers/install_windows.ps1 | 2 +- setup.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build_helpers/install_windows.ps1 b/build_helpers/install_windows.ps1 index 5f0c643ac..4aa070992 100644 --- a/build_helpers/install_windows.ps1 +++ b/build_helpers/install_windows.ps1 @@ -1,6 +1,6 @@ # vendored Wheels compiled via https://github.com/xmatthias/ta-lib-python/tree/ta_bundled_040 -python -m pip install --upgrade "pip<=24.0" wheel +python -m pip install --upgrade pip wheel $pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" diff --git a/setup.sh b/setup.sh index f1317c02c..18f7682d8 100755 --- a/setup.sh +++ b/setup.sh @@ -49,7 +49,7 @@ function updateenv() { source .venv/bin/activate SYS_ARCH=$(uname -m) echo "pip install in-progress. Please wait..." - ${PYTHON} -m pip install --upgrade "pip<=24.0" wheel setuptools + ${PYTHON} -m pip install --upgrade pip wheel setuptools REQUIREMENTS_HYPEROPT="" REQUIREMENTS_PLOT="" REQUIREMENTS_FREQAI="" From af554fc3f770915133793a218938ba436c0efe16 Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Thu, 1 Aug 2024 03:15:58 +0000 Subject: [PATCH 046/242] chore: update pre-commit hooks --- .../exchange/binance_leverage_tiers.json | 586 ++++++++---------- 1 file changed, 244 insertions(+), 342 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 91775fede..ceaa4e486 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -15819,104 +15819,6 @@ } } ], - "GAL/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 11.0, - "info": { - "bracket": "1", - "initialLeverage": "11", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, - "info": { - "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, - "info": { - "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "GALA/USDT:USDT": [ { "tier": 1.0, @@ -28526,6 +28428,250 @@ } ], "REEF/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 26.0, + "info": { + "bracket": "1", + "initialLeverage": "26", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "5000", + "maintMarginRatio": "0.02", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "125.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "750.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10750.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "20750.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83250.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "1500000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333250.0" + } + } + ], + "REN/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "50000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "600000", + "notionalFloor": "50000", + "maintMarginRatio": "0.05", + "cum": "1300.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "1600000", + "notionalFloor": "600000", + "maintMarginRatio": "0.1", + "cum": "31300.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 1600000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.125", + "cum": "71300.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "6000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "321300.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "10000000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.5", + "cum": "1821300.0" + } + } + ], + "RENDER/USDT:USDT": [ { "tier": 1.0, "currency": "USDT", @@ -28655,120 +28801,6 @@ } } ], - "REN/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, - "info": { - "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.015", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, - "info": { - "bracket": "2", - "initialLeverage": "20", - "notionalCap": "50000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "50.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 600000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": "3", - "initialLeverage": "10", - "notionalCap": "600000", - "notionalFloor": "50000", - "maintMarginRatio": "0.05", - "cum": "1300.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 1600000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "1600000", - "notionalFloor": "600000", - "maintMarginRatio": "0.1", - "cum": "31300.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 1600000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "5", - "initialLeverage": "4", - "notionalCap": "2000000", - "notionalFloor": "1600000", - "maintMarginRatio": "0.125", - "cum": "71300.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 6000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "6000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.25", - "cum": "321300.0" - } - }, - { - "tier": 7.0, - "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "7", - "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "6000000", - "maintMarginRatio": "0.5", - "cum": "1821300.0" - } - } - ], "REZ/USDT:USDT": [ { "tier": 1.0, @@ -29127,136 +29159,6 @@ } } ], - "RNDR/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 26.0, - "info": { - "bracket": "1", - "initialLeverage": "26", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.015", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, - "info": { - "bracket": "2", - "initialLeverage": "25", - "notionalCap": "50000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, - "info": { - "bracket": "3", - "initialLeverage": "20", - "notionalCap": "100000", - "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "275.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": "4", - "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "100000", - "maintMarginRatio": "0.05", - "cum": "2775.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "5", - "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.1", - "cum": "52775.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 2500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "6", - "initialLeverage": "4", - "notionalCap": "2500000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.125", - "cum": "102775.0" - } - }, - { - "tier": 7.0, - "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "7", - "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2500000", - "maintMarginRatio": "0.25", - "cum": "415275.0" - } - }, - { - "tier": 8.0, - "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 6000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "8", - "initialLeverage": "1", - "notionalCap": "6000000", - "notionalFloor": "5000000", - "maintMarginRatio": "0.5", - "cum": "1665275.0" - } - } - ], "RONIN/USDT:USDT": [ { "tier": 1.0, From b3ac296cacf071dd0258d50b2480527920491681 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 1 Aug 2024 06:58:17 +0200 Subject: [PATCH 047/242] chore: Improve schema wording --- freqtrade/configuration/config_schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py index 7beed69e6..ca39c10c2 100644 --- a/freqtrade/configuration/config_schema.py +++ b/freqtrade/configuration/config_schema.py @@ -734,7 +734,7 @@ CONF_SCHEMA = { "default": {}, "properties": { "process_throttle_secs": { - "description": "Throttle time in seconds for processing.", + "description": "Minimum loop duration for one bot iteration in seconds.", "type": "integer", }, "interval": { From 8a85077e70679be3f54ad2a09c6b06d48dc6179d Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 1 Aug 2024 07:02:47 +0200 Subject: [PATCH 048/242] chore: add download_trades config key, reorder some keys --- freqtrade/configuration/config_schema.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py index ca39c10c2..89e7f8b3b 100644 --- a/freqtrade/configuration/config_schema.py +++ b/freqtrade/configuration/config_schema.py @@ -36,11 +36,6 @@ CONF_SCHEMA = { "type": ["integer", "number"], "minimum": -1, }, - "new_pairs_days": { - "description": "Download data of new pairs for given number of days", - "type": "integer", - "default": 30, - }, "timeframe": { "description": ( f"The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). {__IN_STRATEGY}" @@ -185,6 +180,7 @@ CONF_SCHEMA = { "type": "boolean", "default": False, }, + # Lookahead analysis section "minimum_trade_amount": { "description": "Minimum amount for a trade - only used for lookahead-analysis", "type": "number", @@ -501,6 +497,7 @@ CONF_SCHEMA = { "required": ["method"], }, }, + # RPC section "telegram": { "description": "Telegram settings.", "type": "object", @@ -701,6 +698,7 @@ CONF_SCHEMA = { }, "required": ["enabled", "listen_ip_address", "listen_port", "username", "password"], }, + # end of RPC section "db_url": { "description": "Database connection URL.", "type": "string", @@ -763,6 +761,16 @@ CONF_SCHEMA = { "description": f"Enable position adjustment. {__IN_STRATEGY}", "type": "boolean", }, + # Download data section + "new_pairs_days": { + "description": "Download data of new pairs for given number of days", + "type": "integer", + "default": 30, + }, + "download_trades": { + "description": "Download trades data by default (instead of ohlcv data).", + "type": "boolean", + }, "max_entry_position_adjustment": { "description": f"Maximum entry position adjustment allowed. {__IN_STRATEGY}", "type": ["integer", "number"], From abef8e376cab91b028b290133548d30827cbb0a2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 1 Aug 2024 07:03:34 +0200 Subject: [PATCH 049/242] feat: add $schema to config examples --- config_examples/config_binance.example.json | 1 + config_examples/config_freqai.example.json | 1 + config_examples/config_full.example.json | 1 + config_examples/config_kraken.example.json | 1 + freqtrade/templates/base_config.json.j2 | 1 + 5 files changed, 5 insertions(+) diff --git a/config_examples/config_binance.example.json b/config_examples/config_binance.example.json index 3a2cea530..6c513f064 100644 --- a/config_examples/config_binance.example.json +++ b/config_examples/config_binance.example.json @@ -1,4 +1,5 @@ { + "$schema": "https://schema.freqtrade.io/schema.json", "max_open_trades": 3, "stake_currency": "USDT", "stake_amount": 0.05, diff --git a/config_examples/config_freqai.example.json b/config_examples/config_freqai.example.json index 27bc4532c..6751002e4 100644 --- a/config_examples/config_freqai.example.json +++ b/config_examples/config_freqai.example.json @@ -1,4 +1,5 @@ { + "$schema": "https://schema.freqtrade.io/schema.json", "trading_mode": "futures", "margin_mode": "isolated", "max_open_trades": 5, diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index cb2d4797e..04137ed80 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -1,4 +1,5 @@ { + "$schema": "https://schema.freqtrade.io/schema.json", "max_open_trades": 3, "stake_currency": "BTC", "stake_amount": 0.05, diff --git a/config_examples/config_kraken.example.json b/config_examples/config_kraken.example.json index 420047627..72f5e6b5f 100644 --- a/config_examples/config_kraken.example.json +++ b/config_examples/config_kraken.example.json @@ -1,4 +1,5 @@ { + "$schema": "https://schema.freqtrade.io/schema.json", "max_open_trades": 5, "stake_currency": "EUR", "stake_amount": 10, diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index 4956cf056..86a717a40 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -6,6 +6,7 @@ "refresh_period": 1800 }' %} { + "$schema": "https://schema.freqtrade.io/schema.json", "max_open_trades": {{ max_open_trades }}, "stake_currency": "{{ stake_currency }}", "stake_amount": {{ stake_amount }}, From 67fdfdf584b081b56dcd8d80b0de738a324fd51f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 1 Aug 2024 19:39:06 +0200 Subject: [PATCH 050/242] chore: Update schema file --- build_helpers/schema.json | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/build_helpers/schema.json b/build_helpers/schema.json index c2b930558..3c37896b1 100644 --- a/build_helpers/schema.json +++ b/build_helpers/schema.json @@ -9,11 +9,6 @@ ], "minimum": -1 }, - "new_pairs_days": { - "description": "Download data of new pairs for given number of days", - "type": "integer", - "default": 30 - }, "timeframe": { "description": "The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). \nUsually specified in the strategy and missing in the configuration.", "type": "string" @@ -1065,7 +1060,7 @@ "default": {}, "properties": { "process_throttle_secs": { - "description": "Throttle time in seconds for processing.", + "description": "Minimum loop duration for one bot iteration in seconds.", "type": "integer" }, "interval": { @@ -1106,6 +1101,15 @@ "description": "Enable position adjustment. \nUsually specified in the strategy and missing in the configuration.", "type": "boolean" }, + "new_pairs_days": { + "description": "Download data of new pairs for given number of days", + "type": "integer", + "default": 30 + }, + "download_trades": { + "description": "Download trades data by default (instead of ohlcv data).", + "type": "boolean" + }, "max_entry_position_adjustment": { "description": "Maximum entry position adjustment allowed. \nUsually specified in the strategy and missing in the configuration.", "type": [ From a8409695120a5a8f6843c5dd2e220d77dd227f29 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 1 Aug 2024 19:58:17 +0200 Subject: [PATCH 051/242] feat: move trades-refresh to async --- freqtrade/exchange/exchange.py | 181 +++++++++++++++++++-------------- 1 file changed, 102 insertions(+), 79 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 3ed35b03d..197e41cb2 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2678,6 +2678,94 @@ class Exchange: self._trades[(pair, timeframe, c_type)] = trades_df return trades_df + async def _build_trades_dl_jobs( + self, pairwt: PairWithTimeframe, data_handler, cache: bool + ) -> Tuple[PairWithTimeframe, Optional[DataFrame]]: + """ + Build coroutines to refresh trades for (they're then called through async.gather) + """ + pair, timeframe, candle_type = pairwt + since_ms = None + new_ticks: List = [] + all_stored_ticks_df = DataFrame(columns=DEFAULT_TRADES_COLUMNS + ["date"]) + first_candle_ms = self.needed_candle_for_trades_ms(timeframe, candle_type) + # refresh, if + # a. not in _trades + # b. no cache used + # c. need new data + is_in_cache = (pair, timeframe, candle_type) in self._trades + if ( + not is_in_cache + or not cache + or self._now_is_time_to_refresh_trades(pair, timeframe, candle_type) + ): + logger.debug(f"Refreshing TRADES data for {pair}") + # fetch trades since latest _trades and + # store together with existing trades + try: + until = None + from_id = None + if is_in_cache: + from_id = self._trades[(pair, timeframe, candle_type)].iloc[-1]["id"] + until = dt_ts() # now + + else: + until = int(timeframe_to_prev_date(timeframe).timestamp()) * 1000 + all_stored_ticks_df = data_handler.trades_load( + f"{pair}-cached", self.trading_mode + ) + + if not all_stored_ticks_df.empty: + if ( + all_stored_ticks_df.iloc[-1]["timestamp"] > first_candle_ms + and all_stored_ticks_df.iloc[0]["timestamp"] <= first_candle_ms + ): + # Use cache and populate further + last_cached_ms = all_stored_ticks_df.iloc[-1]["timestamp"] + from_id = all_stored_ticks_df.iloc[-1]["id"] + # only use cached if it's closer than first_candle_ms + since_ms = ( + last_cached_ms + if last_cached_ms > first_candle_ms + else first_candle_ms + ) + else: + # Skip cache, it's too old + all_stored_ticks_df = DataFrame( + columns=DEFAULT_TRADES_COLUMNS + ["date"] + ) + + # from_id overrules with exchange set to id paginate + [_, new_ticks] = await self._async_get_trade_history( + pair, + since=since_ms if since_ms else first_candle_ms, + until=until, + from_id=from_id, + ) + + except Exception: + logger.exception(f"Refreshing TRADES data for {pair} failed") + return pairwt, None + + if new_ticks: + all_stored_ticks_list = all_stored_ticks_df[DEFAULT_TRADES_COLUMNS].values.tolist() + all_stored_ticks_list.extend(new_ticks) + trades_df = self._process_trades_df( + pair, + timeframe, + candle_type, + all_stored_ticks_list, + cache, + first_required_candle_date=first_candle_ms, + ) + data_handler.trades_store( + f"{pair}-cached", trades_df[DEFAULT_TRADES_COLUMNS], self.trading_mode + ) + return pairwt, trades_df + else: + logger.error(f"No new ticks for {pair}") + return pairwt, None + def refresh_latest_trades( self, pair_list: ListPairsWithTimeframes, @@ -2698,90 +2786,25 @@ class Exchange: self._config["datadir"], data_format=self._config["dataformat_trades"] ) logger.debug("Refreshing TRADES data for %d pairs", len(pair_list)) - since_ms = None results_df = {} - for pair, timeframe, candle_type in set(pair_list): - new_ticks: List = [] - all_stored_ticks_df = DataFrame(columns=DEFAULT_TRADES_COLUMNS + ["date"]) - first_candle_ms = self.needed_candle_for_trades_ms(timeframe, candle_type) - # refresh, if - # a. not in _trades - # b. no cache used - # c. need new data - is_in_cache = (pair, timeframe, candle_type) in self._trades - if ( - not is_in_cache - or not cache - or self._now_is_time_to_refresh_trades(pair, timeframe, candle_type) - ): - logger.debug(f"Refreshing TRADES data for {pair}") - # fetch trades since latest _trades and - # store together with existing trades - try: - until = None - from_id = None - if is_in_cache: - from_id = self._trades[(pair, timeframe, candle_type)].iloc[-1]["id"] - until = dt_ts() # now + coros = [] + for pairwt in set(pair_list): + coros.append(self._build_trades_dl_jobs(pairwt, data_handler, cache)) - else: - until = int(timeframe_to_prev_date(timeframe).timestamp()) * 1000 - all_stored_ticks_df = data_handler.trades_load( - f"{pair}-cached", self.trading_mode - ) + async def gather_stuff(coro): + return await asyncio.gather(*coro, return_exceptions=True) - if not all_stored_ticks_df.empty: - if ( - all_stored_ticks_df.iloc[-1]["timestamp"] > first_candle_ms - and all_stored_ticks_df.iloc[0]["timestamp"] <= first_candle_ms - ): - # Use cache and populate further - last_cached_ms = all_stored_ticks_df.iloc[-1]["timestamp"] - from_id = all_stored_ticks_df.iloc[-1]["id"] - # only use cached if it's closer than first_candle_ms - since_ms = ( - last_cached_ms - if last_cached_ms > first_candle_ms - else first_candle_ms - ) - else: - # Skip cache, it's too old - all_stored_ticks_df = DataFrame( - columns=DEFAULT_TRADES_COLUMNS + ["date"] - ) + for input_coro in chunks(coros, 100): + with self._loop_lock: + results = self.loop.run_until_complete(gather_stuff(input_coro)) - # from_id overrules with exchange set to id paginate - [_, new_ticks] = self.get_historic_trades( - pair, - since=since_ms if since_ms else first_candle_ms, - until=until, - from_id=from_id, - ) - - except Exception: - logger.exception(f"Refreshing TRADES data for {pair} failed") + for res in results: + if isinstance(res, Exception): + logger.warning(f"Async code raised an exception: {repr(res)}") continue - - if new_ticks: - all_stored_ticks_list = all_stored_ticks_df[ - DEFAULT_TRADES_COLUMNS - ].values.tolist() - all_stored_ticks_list.extend(new_ticks) - trades_df = self._process_trades_df( - pair, - timeframe, - candle_type, - all_stored_ticks_list, - cache, - first_required_candle_date=first_candle_ms, - ) - results_df[(pair, timeframe, candle_type)] = trades_df - data_handler.trades_store( - f"{pair}-cached", trades_df[DEFAULT_TRADES_COLUMNS], self.trading_mode - ) - - else: - logger.error(f"No new ticks for {pair}") + pairwt, trades_df = res + if trades_df is not None: + results_df[pairwt] = trades_df return results_df From 9e47172d692f18249005644f1b1681cd8af197db Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 1 Aug 2024 20:37:26 +0200 Subject: [PATCH 052/242] chore: Reduce test flakyness of ws test --- tests/exchange_online/test_ccxt_ws_compat.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/exchange_online/test_ccxt_ws_compat.py b/tests/exchange_online/test_ccxt_ws_compat.py index ed449bb58..78e6e317b 100644 --- a/tests/exchange_online/test_ccxt_ws_compat.py +++ b/tests/exchange_online/test_ccxt_ws_compat.py @@ -30,6 +30,12 @@ class TestCCXTExchangeWs: m_hist = mocker.spy(exch, "_async_get_historic_ohlcv") m_cand = mocker.spy(exch, "_async_get_candle_history") + while True: + # Don't start the test if we are too close to the end of the minute. + if dt_now().second < 50 and dt_now().second != 0: + break + sleep(1) + res = exch.refresh_latest_ohlcv([pair_tf]) assert m_cand.call_count == 1 From dd55baf148901d6d108075c40d5d7bbf29379730 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Aug 2024 07:05:45 +0200 Subject: [PATCH 053/242] chore: support snake_case for api keys --- freqtrade/configuration/config_secrets.py | 4 ++++ freqtrade/exchange/exchange.py | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/freqtrade/configuration/config_secrets.py b/freqtrade/configuration/config_secrets.py index 427e09088..e17a7925e 100644 --- a/freqtrade/configuration/config_secrets.py +++ b/freqtrade/configuration/config_secrets.py @@ -14,12 +14,16 @@ def sanitize_config(config: Config, *, show_sensitive: bool = False) -> Config: return config keys_to_remove = [ "exchange.key", + "exchange.api_key", "exchange.apiKey", "exchange.secret", "exchange.password", "exchange.uid", + "exchange.account_id", "exchange.accountId", + "exchange.wallet_address", "exchange.walletAddress", + "exchange.private_key", "exchange.privateKey", "telegram.token", "telegram.chat_id", diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 3ed35b03d..b80b3147e 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -353,14 +353,18 @@ class Exchange: raise OperationalException(f"Exchange {name} is not supported by ccxt") ex_config = { - "apiKey": exchange_config.get("apiKey", exchange_config.get("key")), + "apiKey": exchange_config.get( + "api_key", exchange_config.get("apiKey", exchange_config.get("key")) + ), "secret": exchange_config.get("secret"), "password": exchange_config.get("password"), "uid": exchange_config.get("uid", ""), - "accountId": exchange_config.get("accountId", ""), + "accountId": exchange_config.get("account_id", exchange_config.get("accountId", "")), # DEX attributes: - "walletAddress": exchange_config.get("walletAddress"), - "privateKey": exchange_config.get("privateKey"), + "walletAddress": exchange_config.get( + "wallet_address", exchange_config.get("walletAddress") + ), + "privateKey": exchange_config.get("private_key", exchange_config.get("privateKey")), } if ccxt_kwargs: logger.info("Applying additional ccxt config: %s", ccxt_kwargs) From 2b0b1e23eb8e5f113693fd4c9d763d4daab3e304 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Aug 2024 07:25:53 +0200 Subject: [PATCH 054/242] chore: enhance error message on ohlcv error --- freqtrade/exchange/exchange.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b80b3147e..c1608e3c1 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2612,12 +2612,13 @@ class Exchange: except (ccxt.OperationFailed, ccxt.ExchangeError) as e: raise TemporaryError( f"Could not fetch historical candle (OHLCV) data " - f"for pair {pair} due to {e.__class__.__name__}. " + f"for {pair}, {timeframe}, {candle_type} due to {e.__class__.__name__}. " f"Message: {e}" ) from e except ccxt.BaseError as e: raise OperationalException( - f"Could not fetch historical candle (OHLCV) data for pair {pair}. Message: {e}" + f"Could not fetch historical candle (OHLCV) data for " + f"{pair}, {timeframe}, {candle_type}. Message: {e}" ) from e async def _fetch_funding_rate_history( From 9429657a2b526a9cb11d52f87fc79e4e299e9179 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Aug 2024 07:28:09 +0200 Subject: [PATCH 055/242] chore: make Hyperliquid class actually usable --- freqtrade/exchange/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index fc92ed5a9..d52f94293 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -39,6 +39,7 @@ from freqtrade.exchange.exchange_utils_timeframe import ( from freqtrade.exchange.gate import Gate from freqtrade.exchange.hitbtc import Hitbtc from freqtrade.exchange.htx import Htx +from freqtrade.exchange.hyperliquid import Hyperliquid from freqtrade.exchange.idex import Idex from freqtrade.exchange.kraken import Kraken from freqtrade.exchange.kucoin import Kucoin From 1760624954938c119905d1dd49c3ad1134460ec4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Aug 2024 19:48:43 +0200 Subject: [PATCH 056/242] test: Test "invalid date format" --- tests/test_configuration.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index af482a965..6943549d9 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -855,6 +855,10 @@ def test_validate_whitelist(default_conf): [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "20:02"}], None, ), + ( + [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "55:102"}], + "Invalid date format for unlock_at: 55:102.", + ), ], ) def test_validate_protections(default_conf, protconf, expected): From 57139295b5b24a2fb7488f358e0c7b575ab270de Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Aug 2024 20:12:44 +0200 Subject: [PATCH 057/242] tests: Add unlock_at test --- tests/plugins/test_protections.py | 83 +++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index c537eb035..3fb27ce3d 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -423,6 +423,89 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog): assert not PairLocks.is_global_lock() +@pytest.mark.usefixtures("init_persistence") +def test_CooldownPeriod_unlock_at(mocker, default_conf, fee, caplog, time_machine): + default_conf["protections"] = [ + { + "method": "CooldownPeriod", + "unlock_at": "05:00", + } + ] + freqtrade = get_patched_freqtradebot(mocker, default_conf) + message = r"Trading stopped due to .*" + assert not freqtrade.protections.global_stop() + assert not freqtrade.protections.stop_per_pair("XRP/BTC") + + assert not log_has_re(message, caplog) + caplog.clear() + + start_dt = datetime(2024, 5, 2, 0, 30, 0, tzinfo=timezone.utc) + time_machine.move_to(start_dt, tick=False) + + generate_mock_trade( + "XRP/BTC", + fee.return_value, + False, + exit_reason=ExitType.STOP_LOSS.value, + min_ago_open=20, + min_ago_close=10, + ) + + assert not freqtrade.protections.global_stop() + assert freqtrade.protections.stop_per_pair("XRP/BTC") + assert PairLocks.is_pair_locked("XRP/BTC") + assert not PairLocks.is_global_lock() + + # Move time to "4:30" + time_machine.move_to(start_dt + timedelta(hours=4), tick=False) + assert PairLocks.is_pair_locked("XRP/BTC") + assert not PairLocks.is_global_lock() + + # Move time to "past 5:00" + time_machine.move_to(start_dt + timedelta(hours=5), tick=False) + assert not PairLocks.is_pair_locked("XRP/BTC") + assert not PairLocks.is_global_lock() + + # Force rollover to the next day. + start_dt = datetime(2024, 5, 2, 22, 00, 0, tzinfo=timezone.utc) + time_machine.move_to(start_dt, tick=False) + generate_mock_trade( + "ETH/BTC", + fee.return_value, + False, + exit_reason=ExitType.ROI.value, + min_ago_open=20, + min_ago_close=10, + ) + + assert not freqtrade.protections.global_stop() + assert not PairLocks.is_pair_locked("ETH/BTC") + assert freqtrade.protections.stop_per_pair("ETH/BTC") + assert PairLocks.is_pair_locked("ETH/BTC") + assert not PairLocks.is_global_lock() + # Move to 23:00 + time_machine.move_to(start_dt + timedelta(hours=1), tick=False) + assert PairLocks.is_pair_locked("ETH/BTC") + assert not PairLocks.is_global_lock() + + # Move to 04:59 (should still be locked) + time_machine.move_to(start_dt + timedelta(hours=6, minutes=59), tick=False) + assert PairLocks.is_pair_locked("ETH/BTC") + assert not PairLocks.is_global_lock() + + # Move to 05:01 (should still be locked - it unlocks once the 05:00 candle stops at 05:05) + time_machine.move_to(start_dt + timedelta(hours=7, minutes=1), tick=False) + + assert PairLocks.is_pair_locked("ETH/BTC") + assert not PairLocks.is_global_lock() + + # Move to 05:01 (unlocked). + time_machine.move_to(start_dt + timedelta(hours=7, minutes=5), tick=False) + + assert not PairLocks.is_pair_locked("ETH/BTC") + assert not PairLocks.is_global_lock() + + @pytest.mark.parametrize("only_per_side", [False, True]) @pytest.mark.usefixtures("init_persistence") def test_LowProfitPairs(mocker, default_conf, fee, caplog, only_per_side): From 98c8521057b1a6e536600950b05326e49577769a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Aug 2024 20:13:59 +0200 Subject: [PATCH 058/242] chore: fix minor gotcha --- docs/includes/protections.md | 2 +- freqtrade/plugins/protections/cooldown_period.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/includes/protections.md b/docs/includes/protections.md index e64ca0328..a4cb9d3cc 100644 --- a/docs/includes/protections.md +++ b/docs/includes/protections.md @@ -121,7 +121,7 @@ def protections(self): #### Cooldown Period -`CooldownPeriod` locks a pair for `stop_duration` in minutes (or in candles when using `stop_duration_candles`, or until the set time when using `unlock_at`) after selling, avoiding a re-entry for this pair for `stop_duration` minutes. +`CooldownPeriod` locks a pair for `stop_duration` in minutes (or in candles when using `stop_duration_candles`, or until the set time when using `unlock_at`) after exiting, avoiding a re-entry for this pair for `stop_duration` minutes. The below example will stop trading a pair for 2 candles after closing a trade, allowing this pair to "cool down". diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index 9608a51cc..09d506b91 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -30,7 +30,7 @@ class CooldownPeriod(IProtection): """ Get last trade for this pair """ - look_back_until = date_now - timedelta(minutes=self._stop_duration) + look_back_until = date_now - timedelta(minutes=self._lookback_period) # filters = [ # Trade.is_open.is_(False), # Trade.close_date > look_back_until, From f63910d355cbdcacaad0f1e460df25a688e0ee6d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Aug 2024 20:15:46 +0200 Subject: [PATCH 059/242] chore: improve wording for cooldown_period --- freqtrade/plugins/protections/cooldown_period.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index 09d506b91..d30bd87e5 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -42,7 +42,7 @@ class CooldownPeriod(IProtection): # Get latest trade # Ignore type error as we know we only get closed trades. trade = sorted(trades, key=lambda t: t.close_date)[-1] # type: ignore - self.log_once(f"Cooldown for {pair} for {self.stop_duration_str}.", logger.info) + self.log_once(f"Cooldown for {pair} {self.unlock_reason_time_element}.", logger.info) until = self.calculate_lock_end([trade]) return ProtectionReturn( From a6689b1035cc850a2f254c5335ff4f7488d6dc79 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Aug 2024 09:57:31 +0200 Subject: [PATCH 060/242] chore: Remove unnecessary, duplicate mkdocs install --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb3ea2221..db11ba833 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -384,7 +384,6 @@ jobs: - name: Documentation build run: | pip install -r docs/requirements-docs.txt - pip install mkdocs mkdocs build - name: Discord notification From 8bc19494664bfd5ae0d3883c88c0eb43c1a61a1e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Aug 2024 16:41:22 +0200 Subject: [PATCH 061/242] docs: update link to technical documentation --- docs/strategy-customization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 48f629df5..98d7ae9d2 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -158,7 +158,7 @@ Out of the box, freqtrade installs the following technical libraries: - [ta-lib](https://ta-lib.github.io/ta-lib-python/) - [pandas-ta](https://twopirllc.github.io/pandas-ta/) -- [technical](https://github.com/freqtrade/technical/) +- [technical](https://technical.freqtrade.io) Additional technical libraries can be installed as necessary, or custom indicators may be written / invented by the strategy author. From 805c946b33d5d9830043266cb513047f39f17dc9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Aug 2024 18:24:52 +0200 Subject: [PATCH 062/242] feat: improve structure of list_exchange endpoints --- freqtrade/exchange/exchange_utils.py | 17 +++++++++-------- freqtrade/types/valid_exchanges_type.py | 2 ++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index 9c2514f92..19b33d86c 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -53,9 +53,9 @@ def available_exchanges(ccxt_module: Optional[CcxtModuleType] = None) -> List[st return [x for x in exchanges if validate_exchange(x)[0]] -def validate_exchange(exchange: str) -> Tuple[bool, str, bool]: +def validate_exchange(exchange: str) -> Tuple[bool, str, Optional[ccxt.Exchange]]: """ - returns: can_use, reason + returns: can_use, reason, exchange_object with Reason including both missing and missing_opt """ try: @@ -64,11 +64,10 @@ def validate_exchange(exchange: str) -> Tuple[bool, str, bool]: ex_mod = getattr(ccxt.async_support, exchange.lower())() if not ex_mod or not ex_mod.has: - return False, "", False + return False, "", None result = True reason = "" - is_dex = getattr(ex_mod, "dex", False) missing = [ k for k, v in EXCHANGE_HAS_REQUIRED.items() @@ -87,19 +86,21 @@ def validate_exchange(exchange: str) -> Tuple[bool, str, bool]: if missing_opt: reason += f"{'. ' if reason else ''}missing opt: {', '.join(missing_opt)}. " - return result, reason, is_dex + return result, reason, ex_mod def _build_exchange_list_entry( exchange_name: str, exchangeClasses: Dict[str, Any] ) -> ValidExchangesType: - valid, comment, is_dex = validate_exchange(exchange_name) + valid, comment, ex_mod = validate_exchange(exchange_name) result: ValidExchangesType = { - "name": exchange_name, + "name": getattr(ex_mod, "name", exchange_name), + "classname": exchange_name, "valid": valid, "supported": exchange_name.lower() in SUPPORTED_EXCHANGES, "comment": comment, - "dex": is_dex, + "dex": getattr(ex_mod, "dex", False), + "is_alias": getattr(ex_mod, "alias", False), "trade_modes": [{"trading_mode": "spot", "margin_mode": ""}], } if resolved := exchangeClasses.get(exchange_name.lower()): diff --git a/freqtrade/types/valid_exchanges_type.py b/freqtrade/types/valid_exchanges_type.py index 9de05b964..079b2dc59 100644 --- a/freqtrade/types/valid_exchanges_type.py +++ b/freqtrade/types/valid_exchanges_type.py @@ -11,8 +11,10 @@ class TradeModeType(TypedDict): class ValidExchangesType(TypedDict): name: str + classname: str valid: bool supported: bool comment: str dex: bool + is_alias: bool trade_modes: List[TradeModeType] From b3915ff8fdf4366c93218234d196d3a054606470 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Aug 2024 18:25:31 +0200 Subject: [PATCH 063/242] chore: use classname to show exchanges --- freqtrade/commands/list_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 1696fc8f0..f22deee87 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -50,7 +50,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: table.add_column("Reason") for exchange in available_exchanges: - name = Text(exchange["name"]) + name = Text(exchange["classname"]) if exchange["supported"]: name.append(" (Official)", style="italic") name.stylize("green bold") From 9eebe82b34cb7ab13bb365f131ff79bb204c4e35 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Aug 2024 18:29:16 +0200 Subject: [PATCH 064/242] chore: fix api-server tests --- tests/rpc/test_rpc_apiserver.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 22264ae54..98513f290 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -2148,35 +2148,41 @@ def test_api_exchanges(botclient): response = rc.json() assert isinstance(response["exchanges"], list) assert len(response["exchanges"]) > 20 - okx = [x for x in response["exchanges"] if x["name"] == "okx"][0] + okx = [x for x in response["exchanges"] if x["classname"] == "okx"][0] assert okx == { - "name": "okx", + "classname": "okx", + "name": "OKX", "valid": True, "supported": True, "comment": "", "dex": False, + "is_alias": False, "trade_modes": [ {"trading_mode": "spot", "margin_mode": ""}, {"trading_mode": "futures", "margin_mode": "isolated"}, ], } - mexc = [x for x in response["exchanges"] if x["name"] == "mexc"][0] + mexc = [x for x in response["exchanges"] if x["classname"] == "mexc"][0] assert mexc == { - "name": "mexc", + "classname": "mexc", + "name": "MEXC Global", "valid": True, "supported": False, "dex": False, "comment": "", + "is_alias": False, "trade_modes": [{"trading_mode": "spot", "margin_mode": ""}], } - waves = [x for x in response["exchanges"] if x["name"] == "wavesexchange"][0] + waves = [x for x in response["exchanges"] if x["classname"] == "wavesexchange"][0] assert waves == { - "name": "wavesexchange", + "classname": "wavesexchange", + "name": "Waves.Exchange", "valid": True, "supported": False, "dex": True, "comment": ANY, + "is_alias": False, "trade_modes": [{"trading_mode": "spot", "margin_mode": ""}], } From c8d30ae801933162b95ff03e968f3cb1ce416de6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Aug 2024 18:30:21 +0200 Subject: [PATCH 065/242] chore: fix oneline exchange-list output --- freqtrade/commands/list_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index f22deee87..baa2c8c00 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -32,7 +32,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: ) if args["print_one_column"]: - print("\n".join([e["name"] for e in available_exchanges])) + print("\n".join([e["classname"] for e in available_exchanges])) else: if args["list_exchanges_all"]: title = ( From d7ecdc9b07d74def71471dee0fc204b0fd098b00 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Aug 2024 18:31:39 +0200 Subject: [PATCH 066/242] chore: Downgrade cryptography for RPI https://github.com/piwheels/packages/issues/464 --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fc2272fdf..b32df0058 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,8 @@ numexpr==2.10.1 pandas-ta==0.3.14b ccxt==4.3.68 -cryptography==43.0.0 +cryptography==42.0.8; platform_machine == 'armv7l' +cryptography==43.0.0; platform_machine != 'armv7l' aiohttp==3.9.5 SQLAlchemy==2.0.31 python-telegram-bot==21.4 From 4854bdd02fb96757823ed6f342d3d7c23ff0d29d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Aug 2024 08:29:15 +0200 Subject: [PATCH 067/242] chore: Add log_responses to config schema --- build_helpers/schema.json | 5 +++++ freqtrade/configuration/config_schema.py | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/build_helpers/schema.json b/build_helpers/schema.json index 3c37896b1..6951d2cde 100644 --- a/build_helpers/schema.json +++ b/build_helpers/schema.json @@ -1213,6 +1213,11 @@ }, "uniqueItems": true }, + "log_responses": { + "description": "Log responses from the exchange.Useful/required to debug issues with order processing.", + "type": "boolean", + "default": false + }, "unknown_fee_rate": { "description": "Fee rate for unknown markets.", "type": "number" diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py index 89e7f8b3b..9832bbbc7 100644 --- a/freqtrade/configuration/config_schema.py +++ b/freqtrade/configuration/config_schema.py @@ -861,6 +861,14 @@ CONF_SCHEMA = { "items": {"type": "string"}, "uniqueItems": True, }, + "log_responses": { + "description": ( + "Log responses from the exchange." + "Useful/required to debug issues with order processing." + ), + "type": "boolean", + "default": False, + }, "unknown_fee_rate": { "description": "Fee rate for unknown markets.", "type": "number", From 6c5fb5e22b7ca2867e7a3c36649c12eed51793ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Aug 2024 09:03:32 +0200 Subject: [PATCH 068/242] chore: add_config_files to config_schema --- build_helpers/schema.json | 7 +++++++ freqtrade/configuration/config_schema.py | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/build_helpers/schema.json b/build_helpers/schema.json index 6951d2cde..89a7f1efb 100644 --- a/build_helpers/schema.json +++ b/build_helpers/schema.json @@ -1118,6 +1118,13 @@ ], "minimum": -1 }, + "add_config_files": { + "description": "Additional configuration files to load.", + "type": "array", + "items": { + "type": "string" + } + }, "orderflow": { "description": "Settings related to order flow.", "type": "object", diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py index 9832bbbc7..86b081570 100644 --- a/freqtrade/configuration/config_schema.py +++ b/freqtrade/configuration/config_schema.py @@ -776,6 +776,11 @@ CONF_SCHEMA = { "type": ["integer", "number"], "minimum": -1, }, + "add_config_files": { + "description": "Additional configuration files to load.", + "type": "array", + "items": {"type": "string"}, + }, "orderflow": { "description": "Settings related to order flow.", "type": "object", From 366c7e2b9162902d09420026694252816bda6318 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Aug 2024 19:54:51 +0200 Subject: [PATCH 069/242] fix: pin matplotlib to 3.9.0 to fix windows wheels caused by the partial yank of 3.9.1 (only the windows wheels have been deleted). Ref: https://github.com/matplotlib/matplotlib/issues/28551 --- requirements-freqai.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index f2d6bd5f5..e0cd09582 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -6,6 +6,8 @@ scikit-learn==1.5.1 joblib==1.4.2 catboost==1.2.5; 'arm' not in platform_machine +# Temporary downgrade of matplotlib due to https://github.com/matplotlib/matplotlib/issues/28551 +matplotlib==3.9.0 lightgbm==4.5.0 xgboost==2.0.3 tensorboard==2.17.0 From ce8d03ddcea3edd351e533a031e6868e39b0b41e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Aug 2024 19:55:40 +0200 Subject: [PATCH 070/242] chore: improve comment as to why matplotlib is pinned in the first place --- requirements-freqai.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index e0cd09582..1f920d2c7 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -6,6 +6,7 @@ scikit-learn==1.5.1 joblib==1.4.2 catboost==1.2.5; 'arm' not in platform_machine +# Pin Matplotlib - it's depended on by catboost # Temporary downgrade of matplotlib due to https://github.com/matplotlib/matplotlib/issues/28551 matplotlib==3.9.0 lightgbm==4.5.0 From cb4747aed24d168e34e3a2bd0fd707003c2fd8bf Mon Sep 17 00:00:00 2001 From: froggleston Date: Sun, 4 Aug 2024 21:43:00 +0100 Subject: [PATCH 071/242] Add rich table width if jupyter in modules --- freqtrade/util/rich_tables.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/freqtrade/util/rich_tables.py b/freqtrade/util/rich_tables.py index d36bf9004..7ae315025 100644 --- a/freqtrade/util/rich_tables.py +++ b/freqtrade/util/rich_tables.py @@ -37,8 +37,12 @@ def print_rich_table( row_to_add: List[Union[str, Text]] = [r if isinstance(r, Text) else str(r) for r in row] table.add_row(*row_to_add) + width = None + if any(module in ["pytest", "ipykernel"] for module in sys.modules): + width = 200 + console = Console( - width=200 if "pytest" in sys.modules else None, + width=width ) console.print(table) @@ -71,7 +75,11 @@ def print_df_rich_table( row = [_format_value(x, floatfmt=".3f") for x in value_list] table.add_row(*row) + width = None + if any(module in ["pytest", "ipykernel"] for module in sys.modules): + width = 200 + console = Console( - width=200 if "pytest" in sys.modules else None, + width=width ) console.print(table) From 17dc41279cf45f2890c457221356b3c7a3fa3da0 Mon Sep 17 00:00:00 2001 From: froggleston Date: Sun, 4 Aug 2024 21:59:07 +0100 Subject: [PATCH 072/242] Ruff formatting --- freqtrade/util/rich_tables.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/freqtrade/util/rich_tables.py b/freqtrade/util/rich_tables.py index 7ae315025..cfab5cd74 100644 --- a/freqtrade/util/rich_tables.py +++ b/freqtrade/util/rich_tables.py @@ -41,9 +41,7 @@ def print_rich_table( if any(module in ["pytest", "ipykernel"] for module in sys.modules): width = 200 - console = Console( - width=width - ) + console = Console(width=width) console.print(table) @@ -79,7 +77,5 @@ def print_df_rich_table( if any(module in ["pytest", "ipykernel"] for module in sys.modules): width = 200 - console = Console( - width=width - ) + console = Console(width=width) console.print(table) From 1e5154c901bc5dd38d822c7a44f92cf8072a9944 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 03:26:37 +0000 Subject: [PATCH 073/242] chore(deps): bump mkdocs-material in the mkdocs group Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material). Updates `mkdocs-material` from 9.5.30 to 9.5.31 - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.30...9.5.31) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch dependency-group: mkdocs ... 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 47fcdaa32..b5ce1db14 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.6 mkdocs==1.6.0 -mkdocs-material==9.5.30 +mkdocs-material==9.5.31 mdx_truly_sane_lists==1.3 pymdown-extensions==10.9 jinja2==3.1.4 From c9f4db2a4f5677dcd608d2d295b2616ac07809b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 03:26:45 +0000 Subject: [PATCH 074/242] chore(deps): bump fastapi from 0.111.1 to 0.112.0 Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.111.1 to 0.112.0. - [Release notes](https://github.com/fastapi/fastapi/releases) - [Commits](https://github.com/fastapi/fastapi/compare/0.111.1...0.112.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 b32df0058..92ee88a39 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,7 +39,7 @@ orjson==3.10.6 sdnotify==0.3.2 # API Server -fastapi==0.111.1 +fastapi==0.112.0 pydantic==2.8.2 uvicorn==0.30.3 pyjwt==2.8.0 From ea2b12a548e08f6b13458d6112cecb0d13d4b51d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 03:27:05 +0000 Subject: [PATCH 075/242] chore(deps-dev): bump ruff from 0.5.5 to 0.5.6 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.5 to 0.5.6. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.5.5...0.5.6) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 554cf7778..446d1e2f4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==4.0.1 -ruff==0.5.5 +ruff==0.5.6 mypy==1.11.0 pre-commit==3.8.0 pytest==8.3.2 From 1f9c2cd181d585d8e103988a5722dac3b253a790 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 03:27:08 +0000 Subject: [PATCH 076/242] chore(deps): bump technical from 1.4.3 to 1.4.4 Bumps [technical](https://github.com/freqtrade/technical) from 1.4.3 to 1.4.4. - [Release notes](https://github.com/freqtrade/technical/releases) - [Commits](https://github.com/freqtrade/technical/compare/1.4.3...1.4.4) --- updated-dependencies: - dependency-name: technical 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 b32df0058..30d898a76 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ requests==2.32.3 urllib3==2.2.2 jsonschema==4.23.0 TA-Lib==0.4.32 -technical==1.4.3 +technical==1.4.4 tabulate==0.9.0 pycoingecko==3.1.0 jinja2==3.1.4 From e3ba28d767c31d2b207016ec32245cf150904fef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 03:27:23 +0000 Subject: [PATCH 077/242] chore(deps): bump ccxt from 4.3.68 to 4.3.73 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.68 to 4.3.73. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/4.3.68...4.3.73) --- 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 b32df0058..630e5ea10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ bottleneck==1.4.0 numexpr==2.10.1 pandas-ta==0.3.14b -ccxt==4.3.68 +ccxt==4.3.73 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.0; platform_machine != 'armv7l' aiohttp==3.9.5 From 0bee3c9db0ae91e47d228a6f29729b6668be0bbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 03:27:30 +0000 Subject: [PATCH 078/242] chore(deps): bump tqdm from 4.66.4 to 4.66.5 Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.4 to 4.66.5. - [Release notes](https://github.com/tqdm/tqdm/releases) - [Commits](https://github.com/tqdm/tqdm/compare/v4.66.4...v4.66.5) --- updated-dependencies: - dependency-name: tqdm dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-freqai-rl.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt index aa4ae5699..9b808f66f 100644 --- a/requirements-freqai-rl.txt +++ b/requirements-freqai-rl.txt @@ -8,4 +8,4 @@ gymnasium==0.29.1 stable_baselines3==2.3.2 sb3_contrib>=2.2.1 # Progress bar for stable-baselines3 and sb3-contrib -tqdm==4.66.4 +tqdm==4.66.5 From 91da1c3f8bde736bbc6400a2dd352d3603bae51c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 06:33:26 +0000 Subject: [PATCH 079/242] chore(deps): bump uvicorn from 0.30.3 to 0.30.5 Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.30.3 to 0.30.5. - [Release notes](https://github.com/encode/uvicorn/releases) - [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md) - [Commits](https://github.com/encode/uvicorn/compare/0.30.3...0.30.5) --- updated-dependencies: - dependency-name: uvicorn 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 92ee88a39..9dffbeae8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,7 +41,7 @@ sdnotify==0.3.2 # API Server fastapi==0.112.0 pydantic==2.8.2 -uvicorn==0.30.3 +uvicorn==0.30.5 pyjwt==2.8.0 aiofiles==24.1.0 psutil==6.0.0 From 477448114a25ff1b143e2cd69a295bcd32d21162 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 06:34:01 +0000 Subject: [PATCH 080/242] chore(deps-dev): bump mypy from 1.11.0 to 1.11.1 Bumps [mypy](https://github.com/python/mypy) from 1.11.0 to 1.11.1. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.11...v1.11.1) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 446d1e2f4..be3c6f533 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,7 +8,7 @@ coveralls==4.0.1 ruff==0.5.6 -mypy==1.11.0 +mypy==1.11.1 pre-commit==3.8.0 pytest==8.3.2 pytest-asyncio==0.23.8 From 3d439c8c01e0b316ed104b99ec27f7efe3ab2861 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 08:58:46 +0000 Subject: [PATCH 081/242] chore(deps): bump pyjwt from 2.8.0 to 2.9.0 Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.8.0 to 2.9.0. - [Release notes](https://github.com/jpadilla/pyjwt/releases) - [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst) - [Commits](https://github.com/jpadilla/pyjwt/compare/2.8.0...2.9.0) --- updated-dependencies: - dependency-name: pyjwt 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 547f15efc..1078b6d84 100644 --- a/requirements.txt +++ b/requirements.txt @@ -42,7 +42,7 @@ sdnotify==0.3.2 fastapi==0.112.0 pydantic==2.8.2 uvicorn==0.30.5 -pyjwt==2.8.0 +pyjwt==2.9.0 aiofiles==24.1.0 psutil==6.0.0 From 95546e0a7bee1a4a2905f03d6a72785e4b4e4439 Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Tue, 6 Aug 2024 03:02:53 +0000 Subject: [PATCH 082/242] chore: update pre-commit hooks --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 58c43d454..4ccc48526 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,14 +2,14 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pycqa/flake8 - rev: "7.1.0" + rev: "7.1.1" hooks: - id: flake8 additional_dependencies: [Flake8-pyproject] # stages: [push] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.11.0" + rev: "v1.11.1" hooks: - id: mypy exclude: build_helpers @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.5.5' + rev: 'v0.5.6' hooks: - id: ruff From 900922760adc702f31fcfbbccd3a0208d963ec9e Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 7 Aug 2024 20:20:08 +0200 Subject: [PATCH 083/242] feat: add json schema validation docs --- docs/configuration.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index ec8134281..aed34762b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -123,6 +123,19 @@ This is similar to using multiple `--config` parameters, but simpler in usage as If multiple files are in the `add_config_files` section, then they will be assumed to be at identical levels, having the last occurrence override the earlier config (unless a parent already defined such a key). +## Editor autocomplete and validation + +If you are using an editor that supports JSON schema, you can use the schema provided by Freqtrade to get autocompletion and validation of your configuration file by adding the following line to the top of your configuration file: + +``` json +{ + "$schema": "https://schema.freqtrade.io/schema.json", +} +``` + +??? Note "Develop version" + The develop schema is available as `https://schema.freqtrade.io/schema_dev.json` - though we recommend to stick to the stable version for the best experience. + ## Configuration parameters The table below will list all configuration parameters available. From 9d0cd961b475c9002836af1000dff5b74d17aed8 Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Thu, 8 Aug 2024 03:12:08 +0000 Subject: [PATCH 084/242] chore: update pre-commit hooks --- .../exchange/binance_leverage_tiers.json | 382 ++++++++++-------- 1 file changed, 207 insertions(+), 175 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index ceaa4e486..129e3a573 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -639,14 +639,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "75", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.0065", "cum": "0.0" } }, @@ -654,112 +654,128 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "50000", + "initialLeverage": "50", + "notionalCap": "25000", "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" + "maintMarginRatio": "0.01", + "cum": "17.5" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 25000.0, + "maxNotional": 150000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "200000", - "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "275.0" + "initialLeverage": "25", + "notionalCap": "150000", + "notionalFloor": "25000", + "maintMarginRatio": "0.02", + "cum": "267.5" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 150000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "200000", - "maintMarginRatio": "0.05", - "cum": "5275.0" + "initialLeverage": "20", + "notionalCap": "300000", + "notionalFloor": "150000", + "maintMarginRatio": "0.025", + "cum": "1017.5" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 300000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.1", - "cum": "105275.0" + "initialLeverage": "10", + "notionalCap": "3000000", + "notionalFloor": "300000", + "maintMarginRatio": "0.05", + "cum": "8517.5" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "4000000", - "maintMarginRatio": "0.125", - "cum": "205275.0" + "initialLeverage": "5", + "notionalCap": "6000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.1", + "cum": "158517.5" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 6000000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", - "maintMarginRatio": "0.25", - "cum": "830275.0" + "initialLeverage": "4", + "notionalCap": "7500000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.125", + "cum": "308517.5" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 7500000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "15000000", + "notionalFloor": "7500000", + "maintMarginRatio": "0.25", + "cum": "1246017.5" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 15000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "15000000", "maintMarginRatio": "0.5", - "cum": "3330275.0" + "cum": "4996017.5" } } ], @@ -23258,13 +23274,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 20000.0, + "maxNotional": 25000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "2", "initialLeverage": "25", - "notionalCap": "20000", + "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.02", "cum": "25.0" @@ -23273,97 +23289,97 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 25000.0, + "minNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "3", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "20000", + "notionalCap": "50000", + "notionalFloor": "25000", "maintMarginRatio": "0.025", - "cum": "125.0" + "cum": "150.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 200000.0, + "minNotional": 50000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "25000", + "notionalCap": "500000", + "notionalFloor": "50000", "maintMarginRatio": "0.05", - "cum": "750.0" + "cum": "1400.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.1", - "cum": "10750.0" + "cum": "26400.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 500000.0, + "minNotional": 1000000.0, + "maxNotional": 1250000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "400000", + "notionalCap": "1250000", + "notionalFloor": "1000000", "maintMarginRatio": "0.125", - "cum": "20750.0" + "cum": "51400.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1250000.0, + "maxNotional": 2500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "2500000", + "notionalFloor": "1250000", "maintMarginRatio": "0.25", - "cum": "83250.0" + "cum": "207650.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 2500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "5000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.5", - "cum": "333250.0" + "cum": "832650.0" } } ], @@ -31018,13 +31034,13 @@ "tier": 5.0, "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "5", "initialLeverage": "25", - "notionalCap": "1000000", + "notionalCap": "2000000", "notionalFloor": "500000", "maintMarginRatio": "0.02", "cum": "4590.0" @@ -31033,97 +31049,97 @@ { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 2000000.0, + "maxNotional": 2500000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "6", "initialLeverage": "20", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "2500000", + "notionalFloor": "2000000", "maintMarginRatio": "0.025", - "cum": "9590.0" + "cum": "14590.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 2500000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "7", "initialLeverage": "10", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "20000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.05", - "cum": "59590.0" + "cum": "77090.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 15000000.0, + "minNotional": 20000000.0, + "maxNotional": 40000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "8", "initialLeverage": "5", - "notionalCap": "15000000", - "notionalFloor": "5000000", + "notionalCap": "40000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.1", - "cum": "309590.0" + "cum": "1077090.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 15000000.0, - "maxNotional": 20000000.0, + "minNotional": 40000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "9", "initialLeverage": "4", - "notionalCap": "20000000", - "notionalFloor": "15000000", + "notionalCap": "50000000", + "notionalFloor": "40000000", "maintMarginRatio": "0.125", - "cum": "684590.0" + "cum": "2077090.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 20000000.0, - "maxNotional": 50000000.0, + "minNotional": 50000000.0, + "maxNotional": 100000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "10", "initialLeverage": "2", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "notionalCap": "100000000", + "notionalFloor": "50000000", "maintMarginRatio": "0.25", - "cum": "3184590.0" + "cum": "8327090.0" } }, { "tier": 11.0, "currency": "USDT", - "minNotional": 50000000.0, - "maxNotional": 100000000.0, + "minNotional": 100000000.0, + "maxNotional": 200000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "11", "initialLeverage": "1", - "notionalCap": "100000000", - "notionalFloor": "50000000", + "notionalCap": "200000000", + "notionalFloor": "100000000", "maintMarginRatio": "0.5", - "cum": "15684590.0" + "cum": "33327090.0" } } ], @@ -33020,96 +33036,112 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "20", - "notionalCap": "25000", + "initialLeverage": "25", + "notionalCap": "20000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "50.0" + "maintMarginRatio": "0.02", + "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 20000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" + "initialLeverage": "20", + "notionalCap": "30000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "125.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 30000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "200000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "30000", + "maintMarginRatio": "0.05", + "cum": "875.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "10675.0" + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "15875.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 600000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "6", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "73175.0" + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "600000", + "maintMarginRatio": "0.125", + "cum": "30875.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 750000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "1500000", + "notionalFloor": "750000", + "maintMarginRatio": "0.25", + "cum": "124625.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "323175.0" + "cum": "499625.0" } } ], From d453aa849a668498c60f3ce1eeca10c647667e89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 03:26:08 +0000 Subject: [PATCH 085/242] chore(deps): bump python Bumps python from 3.12.4-slim-bookworm to 3.12.5-slim-bookworm. --- updated-dependencies: - dependency-name: python dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e435f7f1e..fbe1de165 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12.4-slim-bookworm as base +FROM python:3.12.5-slim-bookworm as base # Setup env ENV LANG C.UTF-8 From e34a28ee53be831dac69352b49403db8ae2882de Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Aug 2024 06:18:31 +0200 Subject: [PATCH 086/242] chore: dependabot should monitor /docker, too --- .github/dependabot.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ddea42684..635e46214 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,9 @@ version: 2 updates: - package-ecosystem: docker - directory: "/" + directories: + - "/" + - "/docker" schedule: interval: daily open-pull-requests-limit: 10 From cbd178dab28f55b22fbcf3e5a3fa15ac5dc1a9ba Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Aug 2024 06:20:58 +0200 Subject: [PATCH 087/242] chore: bump armhf dockerfile to 3.11 --- docker/Dockerfile.armhf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile.armhf b/docker/Dockerfile.armhf index 688254122..8f4736877 100644 --- a/docker/Dockerfile.armhf +++ b/docker/Dockerfile.armhf @@ -1,4 +1,4 @@ -FROM python:3.11.8-slim-bookworm as base +FROM python:3.11.9-slim-bookworm as base # Setup env ENV LANG C.UTF-8 @@ -17,7 +17,7 @@ RUN mkdir /freqtrade \ && chown ftuser:ftuser /freqtrade \ # Allow sudoers && echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers \ - && pip install --upgrade pip + && pip install --upgrade dpip WORKDIR /freqtrade From 9dd9ae7a2fc913606b4d0305d0699809a91c7c6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 04:22:06 +0000 Subject: [PATCH 088/242] chore(deps): bump sqlalchemy from 2.0.31 to 2.0.32 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.31 to 2.0.32. - [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 2fff01ad2..942d3c183 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ ccxt==4.3.73 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.0; platform_machine != 'armv7l' aiohttp==3.9.5 -SQLAlchemy==2.0.31 +SQLAlchemy==2.0.32 python-telegram-bot==21.4 # can't be hard-pinned due to telegram-bot pinning httpx with ~ httpx>=0.24.1 From ee6e78927f64c4fd07f5249475679fe34ef90065 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 04:22:22 +0000 Subject: [PATCH 089/242] chore(deps): bump python-rapidjson from 1.19 to 1.20 Bumps [python-rapidjson](https://github.com/python-rapidjson/python-rapidjson) from 1.19 to 1.20. - [Changelog](https://github.com/python-rapidjson/python-rapidjson/blob/master/CHANGES.rst) - [Commits](https://github.com/python-rapidjson/python-rapidjson/compare/v1.19...v1.20) --- updated-dependencies: - dependency-name: python-rapidjson dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- ft_client/requirements.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ft_client/requirements.txt b/ft_client/requirements.txt index c0d627247..4e2983ba8 100644 --- a/ft_client/requirements.txt +++ b/ft_client/requirements.txt @@ -1,3 +1,3 @@ # Requirements for freqtrade client library requests==2.32.3 -python-rapidjson==1.19 +python-rapidjson==1.20 diff --git a/requirements.txt b/requirements.txt index 2fff01ad2..994042ebe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,7 +31,7 @@ pyarrow==17.0.0; platform_machine != 'armv7l' py_find_1st==1.1.6 # Load ticker files 30% faster -python-rapidjson==1.19 +python-rapidjson==1.20 # Properly format api responses orjson==3.10.6 From 999ee981f78d7261ffbc9f771322df88e28ef0e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 04:22:36 +0000 Subject: [PATCH 090/242] chore(deps-dev): bump time-machine from 2.14.2 to 2.15.0 Bumps [time-machine](https://github.com/adamchainz/time-machine) from 2.14.2 to 2.15.0. - [Changelog](https://github.com/adamchainz/time-machine/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamchainz/time-machine/compare/2.14.2...2.15.0) --- updated-dependencies: - dependency-name: time-machine 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 be3c6f533..4443c9494 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -19,7 +19,7 @@ pytest-timeout==2.3.1 pytest-xdist==3.6.1 isort==5.13.2 # For datetime mocking -time-machine==2.14.2 +time-machine==2.15.0 # Convert jupyter notebooks to markdown documents nbconvert==7.16.4 From 5650de06274b9953af691dc4f8854ded572ce998 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Aug 2024 06:25:52 +0200 Subject: [PATCH 091/242] chore: dependabot shouldn't update major versions --- .github/dependabot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 635e46214..d6c20a6bc 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,6 +6,9 @@ updates: - "/docker" schedule: interval: daily + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] open-pull-requests-limit: 10 - package-ecosystem: pip From 101dc850a2f30767a50073147dcce455a1f0d8ef Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Aug 2024 08:36:14 +0200 Subject: [PATCH 092/242] Update pre-commit sqlalchemy types --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4ccc48526..2e3a2c182 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - types-requests==2.32.0.20240712 - types-tabulate==0.9.0.20240106 - types-python-dateutil==2.9.0.20240316 - - SQLAlchemy==2.0.31 + - SQLAlchemy==2.0.32 # stages: [push] - repo: https://github.com/pycqa/isort From 85844c8ed4d6a3c637173fcb5dd511e68256893b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 06:37:18 +0000 Subject: [PATCH 093/242] chore(deps): bump ccxt from 4.3.73 to 4.3.76 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.73 to 4.3.76. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/4.3.73...4.3.76) --- 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 b8bfd64ea..8109ca3a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ bottleneck==1.4.0 numexpr==2.10.1 pandas-ta==0.3.14b -ccxt==4.3.73 +ccxt==4.3.76 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.0; platform_machine != 'armv7l' aiohttp==3.9.5 From de70ee117cbaab55ef849819c4547877b03fc4bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 06:37:43 +0000 Subject: [PATCH 094/242] chore(deps): bump matplotlib from 3.9.0 to 3.9.1.post1 Bumps [matplotlib](https://github.com/matplotlib/matplotlib) from 3.9.0 to 3.9.1.post1. - [Release notes](https://github.com/matplotlib/matplotlib/releases) - [Commits](https://github.com/matplotlib/matplotlib/compare/v3.9.0...v3.9.1.post1) --- updated-dependencies: - dependency-name: matplotlib dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-freqai.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index 1f920d2c7..a181ab2b4 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -8,7 +8,7 @@ joblib==1.4.2 catboost==1.2.5; 'arm' not in platform_machine # Pin Matplotlib - it's depended on by catboost # Temporary downgrade of matplotlib due to https://github.com/matplotlib/matplotlib/issues/28551 -matplotlib==3.9.0 +matplotlib==3.9.1.post1 lightgbm==4.5.0 xgboost==2.0.3 tensorboard==2.17.0 From f6040c5f06b20c203a8406fa54d2700514e33344 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 07:53:56 +0000 Subject: [PATCH 095/242] chore(deps): bump aiohttp from 3.9.5 to 3.10.1 Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.9.5 to 3.10.1. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.5...v3.10.1) --- updated-dependencies: - dependency-name: aiohttp 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 8109ca3a3..2cbc6608f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ pandas-ta==0.3.14b ccxt==4.3.76 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.0; platform_machine != 'armv7l' -aiohttp==3.9.5 +aiohttp==3.10.1 SQLAlchemy==2.0.32 python-telegram-bot==21.4 # can't be hard-pinned due to telegram-bot pinning httpx with ~ From d4ca6617de01999575a726a18a5c758c4c141bb2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Aug 2024 20:20:14 +0200 Subject: [PATCH 096/242] chore: set asyncio-policy for windows --- freqtrade/configuration/__init__.py | 1 + freqtrade/configuration/asyncio_config.py | 9 +++++++++ freqtrade/main.py | 2 ++ 3 files changed, 12 insertions(+) create mode 100644 freqtrade/configuration/asyncio_config.py diff --git a/freqtrade/configuration/__init__.py b/freqtrade/configuration/__init__.py index aa06a70c9..8fe65a9b0 100644 --- a/freqtrade/configuration/__init__.py +++ b/freqtrade/configuration/__init__.py @@ -1,5 +1,6 @@ # flake8: noqa: F401 +from freqtrade.configuration.asyncio_config import asyncio_setup from freqtrade.configuration.config_secrets import sanitize_config from freqtrade.configuration.config_setup import setup_utils_configuration from freqtrade.configuration.config_validation import validate_config_consistency diff --git a/freqtrade/configuration/asyncio_config.py b/freqtrade/configuration/asyncio_config.py new file mode 100644 index 000000000..0fdfe51ad --- /dev/null +++ b/freqtrade/configuration/asyncio_config.py @@ -0,0 +1,9 @@ +import asyncio +import sys + + +def asyncio_setup() -> None: + # Set eventloop for win32 setups + + if sys.platform == "win32": + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy) diff --git a/freqtrade/main.py b/freqtrade/main.py index 8161e20a6..67584c5b7 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -15,6 +15,7 @@ if sys.version_info < (3, 9): # pragma: no cover from freqtrade import __version__ from freqtrade.commands import Arguments +from freqtrade.configuration import asyncio_setup from freqtrade.constants import DOCS_LINK from freqtrade.exceptions import ConfigurationError, FreqtradeException, OperationalException from freqtrade.loggers import setup_logging_pre @@ -33,6 +34,7 @@ def main(sysargv: Optional[List[str]] = None) -> None: return_code: Any = 1 try: setup_logging_pre() + asyncio_setup() arguments = Arguments(sysargv) args = arguments.get_parsed_arg() From 758e532a6ab2906debbb65cc1f39722061be7317 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Aug 2024 20:21:02 +0200 Subject: [PATCH 097/242] chore: add todo to uvicorn workaround --- freqtrade/rpc/api_server/uvicorn_threaded.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/rpc/api_server/uvicorn_threaded.py b/freqtrade/rpc/api_server/uvicorn_threaded.py index d717c5567..07faaff53 100644 --- a/freqtrade/rpc/api_server/uvicorn_threaded.py +++ b/freqtrade/rpc/api_server/uvicorn_threaded.py @@ -8,6 +8,7 @@ def asyncio_setup() -> None: # pragma: no cover # Set eventloop for win32 setups # Reverts a change done in uvicorn 0.15.0 - which now sets the eventloop # via policy. + # TODO: is this workaround actually needed? import sys if sys.version_info >= (3, 8) and sys.platform == "win32": From 2b4438720c2b0491d7563245fac4b818f40ff861 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 9 Aug 2024 06:16:44 +0200 Subject: [PATCH 098/242] chore: call selectorPolicy --- freqtrade/configuration/asyncio_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration/asyncio_config.py b/freqtrade/configuration/asyncio_config.py index 0fdfe51ad..8069595fa 100644 --- a/freqtrade/configuration/asyncio_config.py +++ b/freqtrade/configuration/asyncio_config.py @@ -2,8 +2,8 @@ import asyncio import sys -def asyncio_setup() -> None: +def asyncio_setup() -> None: # pragma: no cover # Set eventloop for win32 setups if sys.platform == "win32": - asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy) + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) From ed59f74cb8437eacee9c779d9a25fcba3d193439 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 9 Aug 2024 06:45:08 +0200 Subject: [PATCH 099/242] chore: move asyncio import to only import when necessary --- freqtrade/configuration/asyncio_config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/configuration/asyncio_config.py b/freqtrade/configuration/asyncio_config.py index 8069595fa..c670cce79 100644 --- a/freqtrade/configuration/asyncio_config.py +++ b/freqtrade/configuration/asyncio_config.py @@ -1,4 +1,3 @@ -import asyncio import sys @@ -6,4 +5,6 @@ def asyncio_setup() -> None: # pragma: no cover # Set eventloop for win32 setups if sys.platform == "win32": + import asyncio + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) From 42294ff6957fdf3cce72acbc4039e96bd6eaa6fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:02:14 +0000 Subject: [PATCH 100/242] chore(deps): bump aiohttp from 3.10.1 to 3.10.2 Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.1 to 3.10.2. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.1...v3.10.2) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2cbc6608f..d95f4fe40 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ pandas-ta==0.3.14b ccxt==4.3.76 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.0; platform_machine != 'armv7l' -aiohttp==3.10.1 +aiohttp==3.10.2 SQLAlchemy==2.0.32 python-telegram-bot==21.4 # can't be hard-pinned due to telegram-bot pinning httpx with ~ From f5ebfcca5a9d7940154271dff133dbc132b0783d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 10 Aug 2024 17:11:20 +0200 Subject: [PATCH 101/242] chore: accept that trades dataframes may be empty for some reason part of #10515 --- freqtrade/data/converter/orderflow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index 11d17e52f..ca19a2622 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -78,6 +78,8 @@ def populate_dataframe_with_trades( # create columns for trades _init_dataframe_with_trades_columns(dataframe) + if trades is None or trades.empty: + return dataframe, cached_grouped_trades try: start_time = time.time() @@ -88,7 +90,7 @@ def populate_dataframe_with_trades( max_candles = config_orderflow["max_candles"] start_date = dataframe.tail(max_candles).date.iat[0] # slice of trades that are before current ohlcv candles to make groupby faster - trades = trades.loc[trades.candle_start >= start_date] + trades = trades.loc[trades["candle_start"] >= start_date] trades.reset_index(inplace=True, drop=True) # group trades by candle start From 0afd3fc5e15716fb53fb5049faac3271b9c09401 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 10 Aug 2024 17:51:46 +0200 Subject: [PATCH 102/242] fix: improved handling for corrupt trades files part of #10515 --- freqtrade/data/history/datahandlers/idatahandler.py | 10 +++++++--- freqtrade/exchange/exchange.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/freqtrade/data/history/datahandlers/idatahandler.py b/freqtrade/data/history/datahandlers/idatahandler.py index e335ea770..aea7ea14a 100644 --- a/freqtrade/data/history/datahandlers/idatahandler.py +++ b/freqtrade/data/history/datahandlers/idatahandler.py @@ -247,9 +247,13 @@ class IDataHandler(ABC): :param timerange: Timerange to load trades for - currently not implemented :return: List of trades """ - trades = trades_df_remove_duplicates( - self._trades_load(pair, trading_mode, timerange=timerange) - ) + try: + trades = self._trades_load(pair, trading_mode, timerange=timerange) + except Exception: + logger.exception(f"Error loading trades for {pair}") + return DataFrame(columns=DEFAULT_TRADES_COLUMNS) + + trades = trades_df_remove_duplicates(trades) trades = trades_convert_types(trades) return trades diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index c1608e3c1..631f5587d 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -546,7 +546,7 @@ class Exchange: else: return self._trades[pair_interval] else: - return DataFrame() + return DataFrame(columns=DEFAULT_TRADES_COLUMNS) def get_contract_size(self, pair: str) -> Optional[float]: if self.trading_mode == TradingMode.FUTURES: From 9a9d27b862b4bc15cf04b23a76d6dd7a0772a188 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 03:25:59 +0000 Subject: [PATCH 103/242] chore(deps): bump aiohttp from 3.10.2 to 3.10.3 Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.2 to 3.10.3. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.2...v3.10.3) --- updated-dependencies: - dependency-name: aiohttp 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 d95f4fe40..fea98fa93 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ pandas-ta==0.3.14b ccxt==4.3.76 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.0; platform_machine != 'armv7l' -aiohttp==3.10.2 +aiohttp==3.10.3 SQLAlchemy==2.0.32 python-telegram-bot==21.4 # can't be hard-pinned due to telegram-bot pinning httpx with ~ From 010dbf82f30321b6b3bbad134420808f3d0c537e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 03:26:18 +0000 Subject: [PATCH 104/242] chore(deps-dev): bump ruff from 0.5.6 to 0.5.7 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.6 to 0.5.7. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.5.6...0.5.7) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 4443c9494..0a725f660 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==4.0.1 -ruff==0.5.6 +ruff==0.5.7 mypy==1.11.1 pre-commit==3.8.0 pytest==8.3.2 From fa0ee035e915c475b183955ff69163d3f5c2ac3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 03:26:27 +0000 Subject: [PATCH 105/242] chore(deps): bump orjson from 3.10.6 to 3.10.7 Bumps [orjson](https://github.com/ijl/orjson) from 3.10.6 to 3.10.7. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.10.6...3.10.7) --- updated-dependencies: - dependency-name: orjson 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 d95f4fe40..e79f38a03 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,7 +33,7 @@ py_find_1st==1.1.6 # Load ticker files 30% faster python-rapidjson==1.20 # Properly format api responses -orjson==3.10.6 +orjson==3.10.7 # Notify systemd sdnotify==0.3.2 From 16d5d7b318b0682dab41d264b77d02b972eb2379 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 04:43:08 +0000 Subject: [PATCH 106/242] chore(deps): bump ccxt from 4.3.76 to 4.3.79 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.76 to 4.3.79. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/4.3.76...4.3.79) --- 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 fea98fa93..dc55339f0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ bottleneck==1.4.0 numexpr==2.10.1 pandas-ta==0.3.14b -ccxt==4.3.76 +ccxt==4.3.79 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.0; platform_machine != 'armv7l' aiohttp==3.10.3 From 88b754e38ce163135777760cf230969b02006cfd Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 09:14:36 +0200 Subject: [PATCH 107/242] chore: update test to reflect a fix in ccxt --- tests/exchange_online/test_ccxt_compat.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/exchange_online/test_ccxt_compat.py b/tests/exchange_online/test_ccxt_compat.py index 49fbfc60d..408f47e7d 100644 --- a/tests/exchange_online/test_ccxt_compat.py +++ b/tests/exchange_online/test_ccxt_compat.py @@ -106,9 +106,7 @@ class TestCCXTExchange: assert isinstance(fees, list) for fee in fees: assert isinstance(fee, dict) - assert isinstance(fee["cost"], str) - # TODO: this should be a float! - # assert isinstance(fee["cost"], float) + assert isinstance(fee["cost"], float) assert isinstance(fee["currency"], str) else: From b456afa2ac65570954dd7fb3848f083f7c61eb4c Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 10:05:07 +0200 Subject: [PATCH 108/242] chore: improve backtesting test --- tests/optimize/test_backtesting.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index e9829a8cc..1dce93112 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1486,6 +1486,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) + backtesting.strategy.bot_loop_start = MagicMock() backtesting.strategy.advise_entry = _trend_alternate_hold # Override backtesting.strategy.advise_exit = _trend_alternate_hold # Override @@ -1500,6 +1501,8 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) results = backtesting.backtest(**backtest_conf) + # bot_loop_start is called once per candle. + assert backtesting.strategy.bot_loop_start.call_count == 499 # Make sure we have parallel trades assert len(evaluate_result_multi(results["results"], "5m", 2)) > 0 # make sure we don't have trades with more than configured max_open_trades From e643a2ea32dae4f135d25e4db9d25f226915a15d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 10:37:05 +0200 Subject: [PATCH 109/242] chore: update attribute wording to bt_trades_open --- freqtrade/persistence/trade_model.py | 14 +++++++------- tests/optimize/test_backtest_detail.py | 2 +- tests/optimize/test_backtesting.py | 14 +++++++------- tests/persistence/test_persistence.py | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index e731f7552..6a5991157 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -374,7 +374,7 @@ class LocalTrade: use_db: bool = False # Trades container for backtesting trades: List["LocalTrade"] = [] - trades_open: List["LocalTrade"] = [] + bt_trades_open: List["LocalTrade"] = [] # Copy of trades_open - but indexed by pair bt_trades_open_pp: Dict[str, List["LocalTrade"]] = defaultdict(list) bt_open_open_trade_count: int = 0 @@ -741,7 +741,7 @@ class LocalTrade: Resets all trades. Only active for backtesting mode. """ LocalTrade.trades = [] - LocalTrade.trades_open = [] + LocalTrade.bt_trades_open = [] LocalTrade.bt_trades_open_pp = defaultdict(list) LocalTrade.bt_open_open_trade_count = 0 LocalTrade.total_profit = 0 @@ -1418,13 +1418,13 @@ class LocalTrade: # Offline mode - without database if is_open is not None: if is_open: - sel_trades = LocalTrade.trades_open + sel_trades = LocalTrade.bt_trades_open else: sel_trades = LocalTrade.trades else: # Not used during backtesting, but might be used by a strategy - sel_trades = list(LocalTrade.trades + LocalTrade.trades_open) + sel_trades = list(LocalTrade.trades + LocalTrade.bt_trades_open) if pair: sel_trades = [trade for trade in sel_trades if trade.pair == pair] @@ -1439,7 +1439,7 @@ class LocalTrade: @staticmethod def close_bt_trade(trade): - LocalTrade.trades_open.remove(trade) + LocalTrade.bt_trades_open.remove(trade) LocalTrade.bt_trades_open_pp[trade.pair].remove(trade) LocalTrade.bt_open_open_trade_count -= 1 LocalTrade.trades.append(trade) @@ -1448,7 +1448,7 @@ class LocalTrade: @staticmethod def add_bt_trade(trade): if trade.is_open: - LocalTrade.trades_open.append(trade) + LocalTrade.bt_trades_open.append(trade) LocalTrade.bt_trades_open_pp[trade.pair].append(trade) LocalTrade.bt_open_open_trade_count += 1 else: @@ -1456,7 +1456,7 @@ class LocalTrade: @staticmethod def remove_bt_trade(trade): - LocalTrade.trades_open.remove(trade) + LocalTrade.bt_trades_open.remove(trade) LocalTrade.bt_trades_open_pp[trade.pair].remove(trade) LocalTrade.bt_open_open_trade_count -= 1 diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index edaedb81e..05f8908ee 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -1250,6 +1250,6 @@ def test_backtest_results(default_conf, mocker, caplog, data: BTContainer) -> No assert res.close_date == _get_frame_time_from_offset(trade.close_tick) assert res.is_short == trade.is_short assert len(LocalTrade.trades) == len(data.trades) - assert len(LocalTrade.trades_open) == 0 + assert len(LocalTrade.bt_trades_open) == 0 backtesting.cleanup() del backtesting diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 1dce93112..98912b059 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -529,39 +529,39 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: assert trade.stake_amount == 495 # Fake 2 trades, so there's not enough amount for the next trade left. - LocalTrade.trades_open.append(trade) + LocalTrade.bt_trades_open.append(trade) backtesting.wallets.update() trade = backtesting._enter_trade(pair, row=row, direction="long") assert trade is None - LocalTrade.trades_open.pop() + LocalTrade.bt_trades_open.pop() trade = backtesting._enter_trade(pair, row=row, direction="long") assert trade is not None - LocalTrade.trades_open.pop() + LocalTrade.bt_trades_open.pop() backtesting.strategy.custom_stake_amount = lambda **kwargs: 123.5 backtesting.wallets.update() trade = backtesting._enter_trade(pair, row=row, direction="long") - LocalTrade.trades_open.pop() + LocalTrade.bt_trades_open.pop() assert trade assert trade.stake_amount == 123.5 # In case of error - use proposed stake backtesting.strategy.custom_stake_amount = lambda **kwargs: 20 / 0 trade = backtesting._enter_trade(pair, row=row, direction="long") - LocalTrade.trades_open.pop() + LocalTrade.bt_trades_open.pop() assert trade assert trade.stake_amount == 495 assert trade.is_short is False trade = backtesting._enter_trade(pair, row=row, direction="short") - LocalTrade.trades_open.pop() + LocalTrade.bt_trades_open.pop() assert trade assert trade.stake_amount == 495 assert trade.is_short is True mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=300.0) trade = backtesting._enter_trade(pair, row=row, direction="long") - LocalTrade.trades_open.pop() + LocalTrade.bt_trades_open.pop() assert trade assert trade.stake_amount == 300.0 diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index 0545ac861..ac153deaf 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -2137,7 +2137,7 @@ def test_Trade_object_idem(): ) EXCLUDES2 = ( "trades", - "trades_open", + "bt_trades_open", "bt_trades_open_pp", "bt_open_open_trade_count", "total_profit", From 2bc9cdafb20bb90d86b2cab662ba95266db7d590 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 10:37:56 +0200 Subject: [PATCH 110/242] chore: update attribute wording to bt_trades --- freqtrade/optimize/backtesting.py | 2 +- freqtrade/persistence/trade_model.py | 16 ++++++++-------- freqtrade/rpc/api_server/api_backtest.py | 2 +- tests/optimize/test_backtest_detail.py | 2 +- tests/optimize/test_backtesting.py | 4 ++-- tests/persistence/test_persistence.py | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index c28c080f5..471fdca81 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1491,7 +1491,7 @@ class Backtesting: self.handle_left_open(LocalTrade.bt_trades_open_pp, data=data) self.wallets.update() - results = trade_list_to_dataframe(LocalTrade.trades) + results = trade_list_to_dataframe(LocalTrade.bt_trades) return { "results": results, "config": self.strategy.config, diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 6a5991157..47b17c39d 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -373,7 +373,7 @@ class LocalTrade: use_db: bool = False # Trades container for backtesting - trades: List["LocalTrade"] = [] + bt_trades: List["LocalTrade"] = [] bt_trades_open: List["LocalTrade"] = [] # Copy of trades_open - but indexed by pair bt_trades_open_pp: Dict[str, List["LocalTrade"]] = defaultdict(list) @@ -740,7 +740,7 @@ class LocalTrade: """ Resets all trades. Only active for backtesting mode. """ - LocalTrade.trades = [] + LocalTrade.bt_trades = [] LocalTrade.bt_trades_open = [] LocalTrade.bt_trades_open_pp = defaultdict(list) LocalTrade.bt_open_open_trade_count = 0 @@ -1405,7 +1405,7 @@ class LocalTrade: Helper function to query Trades. Returns a List of trades, filtered on the parameters given. In live mode, converts the filter to a database query and returns all rows - In Backtest mode, uses filters on Trade.trades to get the result. + In Backtest mode, uses filters on Trade.bt_trades to get the result. :param pair: Filter by pair :param is_open: Filter by open/closed status @@ -1420,11 +1420,11 @@ class LocalTrade: if is_open: sel_trades = LocalTrade.bt_trades_open else: - sel_trades = LocalTrade.trades + sel_trades = LocalTrade.bt_trades else: # Not used during backtesting, but might be used by a strategy - sel_trades = list(LocalTrade.trades + LocalTrade.bt_trades_open) + sel_trades = list(LocalTrade.bt_trades + LocalTrade.bt_trades_open) if pair: sel_trades = [trade for trade in sel_trades if trade.pair == pair] @@ -1442,7 +1442,7 @@ class LocalTrade: LocalTrade.bt_trades_open.remove(trade) LocalTrade.bt_trades_open_pp[trade.pair].remove(trade) LocalTrade.bt_open_open_trade_count -= 1 - LocalTrade.trades.append(trade) + LocalTrade.bt_trades.append(trade) LocalTrade.total_profit += trade.close_profit_abs @staticmethod @@ -1452,7 +1452,7 @@ class LocalTrade: LocalTrade.bt_trades_open_pp[trade.pair].append(trade) LocalTrade.bt_open_open_trade_count += 1 else: - LocalTrade.trades.append(trade) + LocalTrade.bt_trades.append(trade) @staticmethod def remove_bt_trade(trade): @@ -1761,7 +1761,7 @@ class Trade(ModelBase, LocalTrade): Helper function to query Trades.j Returns a List of trades, filtered on the parameters given. In live mode, converts the filter to a database query and returns all rows - In Backtest mode, uses filters on Trade.trades to get the result. + In Backtest mode, uses filters on Trade.bt_trades to get the result. :return: unsorted List[Trade] """ diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 42b09de0a..4295d9d19 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -182,7 +182,7 @@ def api_get_backtest(): ApiBG.bt["bt"].progress.action if ApiBG.bt["bt"] else str(BacktestState.STARTUP) ), "progress": ApiBG.bt["bt"].progress.progress if ApiBG.bt["bt"] else 0, - "trade_count": len(LocalTrade.trades), + "trade_count": len(LocalTrade.bt_trades), "status_msg": "Backtest running", } diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 05f8908ee..e0a9e4480 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -1249,7 +1249,7 @@ def test_backtest_results(default_conf, mocker, caplog, data: BTContainer) -> No assert res.open_date == _get_frame_time_from_offset(trade.open_tick) assert res.close_date == _get_frame_time_from_offset(trade.close_tick) assert res.is_short == trade.is_short - assert len(LocalTrade.trades) == len(data.trades) + assert len(LocalTrade.bt_trades) == len(data.trades) assert len(LocalTrade.bt_trades_open) == 0 backtesting.cleanup() del backtesting diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 98912b059..a7823c883 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1041,7 +1041,7 @@ def test_backtest_one_detail_futures( <= round(t["close_rate"], 6) <= round(ln2.iloc[0]["high"], 6) ) - assert pytest.approx(Trade.trades[1].funding_fees) == exp_funding_fee + assert pytest.approx(Trade.bt_trades[1].funding_fees) == exp_funding_fee assert ff_spy.call_count == exp_ff_updates # assert late_entry > 0 @@ -1136,7 +1136,7 @@ def test_backtest_one_detail_futures_funding_fees( # Additional counts will happen due each successful entry, which needs to call this, too. assert ff_spy.call_count == ff_updates - for t in Trade.trades: + for t in Trade.bt_trades: # At least 6 adjustment orders assert t.nr_of_successful_entries == entries # Funding fees will vary depending on the number of adjustment orders diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index ac153deaf..adc2941bf 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -2136,7 +2136,7 @@ def test_Trade_object_idem(): "custom_data", ) EXCLUDES2 = ( - "trades", + "bt_trades", "bt_trades_open", "bt_trades_open_pp", "bt_open_open_trade_count", From 10f0522a6bb84184affa2949d0afe17c4107ccb1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 10:38:57 +0200 Subject: [PATCH 111/242] chore: update attribute wording to bt_profit --- freqtrade/persistence/trade_model.py | 6 +++--- freqtrade/wallets.py | 2 +- tests/persistence/test_persistence.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 47b17c39d..eab4da4f3 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -378,7 +378,7 @@ class LocalTrade: # Copy of trades_open - but indexed by pair bt_trades_open_pp: Dict[str, List["LocalTrade"]] = defaultdict(list) bt_open_open_trade_count: int = 0 - total_profit: float = 0 + bt_total_profit: float = 0 realized_profit: float = 0 id: int = 0 @@ -744,7 +744,7 @@ class LocalTrade: LocalTrade.bt_trades_open = [] LocalTrade.bt_trades_open_pp = defaultdict(list) LocalTrade.bt_open_open_trade_count = 0 - LocalTrade.total_profit = 0 + LocalTrade.bt_total_profit = 0 def adjust_min_max_rates(self, current_price: float, current_price_low: float) -> None: """ @@ -1443,7 +1443,7 @@ class LocalTrade: LocalTrade.bt_trades_open_pp[trade.pair].remove(trade) LocalTrade.bt_open_open_trade_count -= 1 LocalTrade.bt_trades.append(trade) - LocalTrade.total_profit += trade.close_profit_abs + LocalTrade.bt_total_profit += trade.close_profit_abs @staticmethod def add_bt_trade(trade): diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 7f839cb24..1e7281ddd 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -82,7 +82,7 @@ class Wallets: tot_profit = Trade.get_total_closed_profit() else: # Backtest mode - tot_profit = LocalTrade.total_profit + tot_profit = LocalTrade.bt_total_profit tot_profit += sum(trade.realized_profit for trade in open_trades) tot_in_trades = sum(trade.stake_amount for trade in open_trades) used_stake = 0.0 diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index adc2941bf..7ebaf715c 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -2140,7 +2140,7 @@ def test_Trade_object_idem(): "bt_trades_open", "bt_trades_open_pp", "bt_open_open_trade_count", - "total_profit", + "bt_total_profit", "from_json", ) From 980b81f00985bc4f58f38d1411547bba27f3dac2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 14:51:28 +0200 Subject: [PATCH 112/242] chore: Simplify futures backtest --- freqtrade/optimize/backtesting.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 471fdca81..c9bdf4c65 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -329,15 +329,15 @@ class Backtesting: else: self.detail_data = {} if self.trading_mode == TradingMode.FUTURES: - self.funding_fee_timeframe: str = self.exchange.get_option("funding_fee_timeframe") - self.funding_fee_timeframe_secs: int = timeframe_to_seconds(self.funding_fee_timeframe) + funding_fee_timeframe: str = self.exchange.get_option("funding_fee_timeframe") + self.funding_fee_timeframe_secs: int = timeframe_to_seconds(funding_fee_timeframe) mark_timeframe: str = self.exchange.get_option("mark_ohlcv_timeframe") # Load additional futures data. funding_rates_dict = history.load_data( datadir=self.config["datadir"], pairs=self.pairlists.whitelist, - timeframe=self.funding_fee_timeframe, + timeframe=funding_fee_timeframe, timerange=self.timerange, startup_candles=0, fail_without_data=True, From f01e10144784472b91dafdc4d0e711ea77eda62a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 10:19:33 +0200 Subject: [PATCH 113/242] feat: extract backtest iteration into generator --- freqtrade/optimize/backtesting.py | 149 ++++++++++++++++-------------- 1 file changed, 81 insertions(+), 68 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index c9bdf4c65..b26013a11 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1388,6 +1388,25 @@ class Backtesting: self._process_exit_order(order, trade, current_time, row, pair) return open_trade_count_start + def time_pair_generator( + self, start_date: datetime, end_date: datetime, increment: timedelta, pairs: List[str] + ): + """ + Backtest time and pair generator + """ + current_time = start_date + increment + self.progress.init_step( + BacktestState.BACKTEST, int((end_date - start_date) / self.timeframe_td) + ) + while current_time <= end_date: + is_first = True + for pair in pairs: + yield current_time, pair, is_first + is_first = False + + self.progress.increment() + current_time += increment + def backtest(self, processed: Dict, start_date: datetime, end_date: datetime) -> Dict[str, Any]: """ Implement backtesting functionality @@ -1411,82 +1430,76 @@ class Backtesting: # Indexes per pair, so some pairs are allowed to have a missing start. indexes: Dict = defaultdict(int) - current_time = start_date + self.timeframe_td - self.progress.init_step( - BacktestState.BACKTEST, int((end_date - start_date) / self.timeframe_td) - ) # Loop timerange and get candle for each pair at that point in time - while current_time <= end_date: - open_trade_count_start = LocalTrade.bt_open_open_trade_count - self.check_abort() - strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)( - current_time=current_time - ) - for i, pair in enumerate(data): - row_index = indexes[pair] - row = self.validate_row(data, pair, row_index, current_time) - if not row: - continue + for current_time, pair, is_first in self.time_pair_generator( + start_date, end_date, self.timeframe_td, list(data.keys()) + ): + if is_first: + open_trade_count_start = LocalTrade.bt_open_open_trade_count + self.check_abort() + strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)( + current_time=current_time + ) + row_index = indexes[pair] + row = self.validate_row(data, pair, row_index, current_time) + if not row: + continue - row_index += 1 - indexes[pair] = row_index - self.dataprovider._set_dataframe_max_index(self.required_startup + row_index) - self.dataprovider._set_dataframe_max_date(current_time) - current_detail_time: datetime = row[DATE_IDX].to_pydatetime() - trade_dir: Optional[LongShort] = self.check_for_trade_entry(row) + row_index += 1 + indexes[pair] = row_index + self.dataprovider._set_dataframe_max_index(self.required_startup + row_index) + self.dataprovider._set_dataframe_max_date(current_time) + current_detail_time: datetime = row[DATE_IDX].to_pydatetime() + trade_dir: Optional[LongShort] = self.check_for_trade_entry(row) - if ( - (trade_dir is not None or len(LocalTrade.bt_trades_open_pp[pair]) > 0) - and self.timeframe_detail - and pair in self.detail_data - ): - # Spread out into detail timeframe. - # Should only happen when we are either in a trade for this pair - # or when we got the signal for a new trade. - exit_candle_end = current_detail_time + self.timeframe_td + if ( + (trade_dir is not None or len(LocalTrade.bt_trades_open_pp[pair]) > 0) + and self.timeframe_detail + and pair in self.detail_data + ): + # Spread out into detail timeframe. + # Should only happen when we are either in a trade for this pair + # or when we got the signal for a new trade. + exit_candle_end = current_detail_time + self.timeframe_td - detail_data = self.detail_data[pair] - detail_data = detail_data.loc[ - (detail_data["date"] >= current_detail_time) - & (detail_data["date"] < exit_candle_end) - ].copy() - if len(detail_data) == 0: - # Fall back to "regular" data if no detail data was found for this candle - open_trade_count_start = self.backtest_loop( - row, pair, current_time, end_date, open_trade_count_start, trade_dir - ) - continue - detail_data.loc[:, "enter_long"] = row[LONG_IDX] - detail_data.loc[:, "exit_long"] = row[ELONG_IDX] - detail_data.loc[:, "enter_short"] = row[SHORT_IDX] - detail_data.loc[:, "exit_short"] = row[ESHORT_IDX] - detail_data.loc[:, "enter_tag"] = row[ENTER_TAG_IDX] - detail_data.loc[:, "exit_tag"] = row[EXIT_TAG_IDX] - is_first = True - current_time_det = current_time - for det_row in detail_data[HEADERS].values.tolist(): - self.dataprovider._set_dataframe_max_date(current_time_det) - open_trade_count_start = self.backtest_loop( - det_row, - pair, - current_time_det, - end_date, - open_trade_count_start, - trade_dir, - is_first, - ) - current_time_det += self.timeframe_detail_td - is_first = False - else: - self.dataprovider._set_dataframe_max_date(current_time) + detail_data = self.detail_data[pair] + detail_data = detail_data.loc[ + (detail_data["date"] >= current_detail_time) + & (detail_data["date"] < exit_candle_end) + ].copy() + if len(detail_data) == 0: + # Fall back to "regular" data if no detail data was found for this candle open_trade_count_start = self.backtest_loop( row, pair, current_time, end_date, open_trade_count_start, trade_dir ) - - # Move time one configured time_interval ahead. - self.progress.increment() - current_time += self.timeframe_td + continue + detail_data.loc[:, "enter_long"] = row[LONG_IDX] + detail_data.loc[:, "exit_long"] = row[ELONG_IDX] + detail_data.loc[:, "enter_short"] = row[SHORT_IDX] + detail_data.loc[:, "exit_short"] = row[ESHORT_IDX] + detail_data.loc[:, "enter_tag"] = row[ENTER_TAG_IDX] + detail_data.loc[:, "exit_tag"] = row[EXIT_TAG_IDX] + is_first = True + current_time_det = current_time + for det_row in detail_data[HEADERS].values.tolist(): + self.dataprovider._set_dataframe_max_date(current_time_det) + open_trade_count_start = self.backtest_loop( + det_row, + pair, + current_time_det, + end_date, + open_trade_count_start, + trade_dir, + is_first, + ) + current_time_det += self.timeframe_detail_td + is_first = False + else: + self.dataprovider._set_dataframe_max_date(current_time) + open_trade_count_start = self.backtest_loop( + row, pair, current_time, end_date, open_trade_count_start, trade_dir + ) self.handle_left_open(LocalTrade.bt_trades_open_pp, data=data) self.wallets.update() From b6f4e124ce4f249972eb6b849f0260457d9262d7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 11:36:54 +0200 Subject: [PATCH 114/242] chore: improve backtesting test details ensure all candles used the same pairlist ordering --- tests/optimize/test_backtesting.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index a7823c883..c2c6ce954 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1,6 +1,7 @@ # pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument import random +from collections import defaultdict from copy import deepcopy from datetime import datetime, timedelta, timezone from pathlib import Path @@ -1485,6 +1486,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) default_conf["max_open_trades"] = 3 backtesting = Backtesting(default_conf) + vr_spy = mocker.spy(backtesting, "validate_row") backtesting._set_strategy(backtesting.strategylist[0]) backtesting.strategy.bot_loop_start = MagicMock() backtesting.strategy.advise_entry = _trend_alternate_hold # Override @@ -1503,6 +1505,17 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) # bot_loop_start is called once per candle. assert backtesting.strategy.bot_loop_start.call_count == 499 + # Validated row once per candle and pair + assert vr_spy.call_count == 2495 + # List of calls pair args - in batches of 5 (s) + calls_per_candle = defaultdict(list) + for call in vr_spy.call_args_list: + calls_per_candle[call[0][3]].append(call[0][1]) + + all_orients = [x for _, x in calls_per_candle.items()] + + assert all(x == ["ADA/BTC", "DASH/BTC", "ETH/BTC", "LTC/BTC", "NXT/BTC"] for x in all_orients) + # Make sure we have parallel trades assert len(evaluate_result_multi(results["results"], "5m", 2)) > 0 # make sure we don't have trades with more than configured max_open_trades From 7945eba38658b7e9e07f240b73fe779ba2a7279f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 11:51:36 +0200 Subject: [PATCH 115/242] feat: Evaluate pairs with open trades first This will enable further improved logic for pairs with no open trade. --- freqtrade/optimize/backtesting.py | 5 ++++- tests/optimize/test_backtesting.py | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index b26013a11..b6b7c113f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1400,7 +1400,10 @@ class Backtesting: ) while current_time <= end_date: is_first = True - for pair in pairs: + # Pairs that have open trades should be processed first + new_pairlist = list(dict.fromkeys([t.pair for t in LocalTrade.bt_trades_open] + pairs)) + + for pair in new_pairlist: yield current_time, pair, is_first is_first = False diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index c2c6ce954..9567a2cac 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1514,7 +1514,26 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) all_orients = [x for _, x in calls_per_candle.items()] - assert all(x == ["ADA/BTC", "DASH/BTC", "ETH/BTC", "LTC/BTC", "NXT/BTC"] for x in all_orients) + distinct_calls = [list(x) for x in set(tuple(x) for x in all_orients)] + + # All calls must be made for the full pairlist + assert all(len(x) == 5 for x in distinct_calls) + + # order varied - and is not always identical + assert not all( + x == ["ADA/BTC", "DASH/BTC", "ETH/BTC", "LTC/BTC", "NXT/BTC"] for x in distinct_calls + ) + # But some calls should've kept the original ordering + assert any( + x == ["ADA/BTC", "DASH/BTC", "ETH/BTC", "LTC/BTC", "NXT/BTC"] for x in distinct_calls + ) + assert ( + # Ordering can be different, but should be one of the following + any(x == ["ETH/BTC", "ADA/BTC", "DASH/BTC", "LTC/BTC", "NXT/BTC"] for x in distinct_calls) + or any( + x == ["ETH/BTC", "LTC/BTC", "ADA/BTC", "DASH/BTC", "NXT/BTC"] for x in distinct_calls + ) + ) # Make sure we have parallel trades assert len(evaluate_result_multi(results["results"], "5m", 2)) > 0 From 08c10c1f9b51ed162bd4fbc2c0f568e4f82f7811 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 12:34:51 +0200 Subject: [PATCH 116/242] chore: exclude right boundary from parallelism test --- freqtrade/data/btanalysis.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 2895b4181..a237b10f1 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -401,7 +401,15 @@ def analyze_trade_parallelism(results: pd.DataFrame, timeframe: str) -> pd.DataF timeframe_freq = timeframe_to_resample_freq(timeframe) dates = [ - pd.Series(pd.date_range(row[1]["open_date"], row[1]["close_date"], freq=timeframe_freq)) + pd.Series( + pd.date_range( + row[1]["open_date"], + row[1]["close_date"], + freq=timeframe_freq, + # Exclude right boundary - the date is the candle open date. + inclusive="left", + ) + ) for row in results[["open_date", "close_date"]].iterrows() ] deltas = [len(x) for x in dates] From 70f3018e67970556237d8df52c885623899edbc4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 13:19:53 +0200 Subject: [PATCH 117/242] feat: remove "open_trade_count_start" workaround Due to the updated pair ordering logic, we can open trades on different pairs during the same candle without superating the max_open_trades limit --- freqtrade/optimize/backtesting.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index b6b7c113f..2edb65e1b 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1332,10 +1332,9 @@ class Backtesting: pair: str, current_time: datetime, end_date: datetime, - open_trade_count_start: int, trade_dir: Optional[LongShort], is_first: bool = True, - ) -> int: + ) -> None: """ NOTE: This method is used by Hyperopt at each iteration. Please keep it optimized. @@ -1345,7 +1344,6 @@ class Backtesting: # 1. Manage currently open orders of active trades if self.manage_open_orders(t, current_time, row): # Close trade - open_trade_count_start -= 1 LocalTrade.remove_bt_trade(t) self.wallets.update() @@ -1361,13 +1359,9 @@ class Backtesting: and trade_dir is not None and not PairLocks.is_pair_locked(pair, row[DATE_IDX], trade_dir) ): - if self.trade_slot_available(open_trade_count_start): + if self.trade_slot_available(LocalTrade.bt_open_open_trade_count): trade = self._enter_trade(pair, row, trade_dir) if trade: - # TODO: hacky workaround to avoid opening > max_open_trades - # This emulates previous behavior - not sure if this is correct - # Prevents entering if the trade-slot was freed in this candle - open_trade_count_start += 1 self.wallets.update() else: self._collate_rejected(pair, row) @@ -1386,7 +1380,6 @@ class Backtesting: order = trade.select_order(trade.exit_side, is_open=True) if order: self._process_exit_order(order, trade, current_time, row, pair) - return open_trade_count_start def time_pair_generator( self, start_date: datetime, end_date: datetime, increment: timedelta, pairs: List[str] @@ -1439,7 +1432,6 @@ class Backtesting: start_date, end_date, self.timeframe_td, list(data.keys()) ): if is_first: - open_trade_count_start = LocalTrade.bt_open_open_trade_count self.check_abort() strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)( current_time=current_time @@ -1473,9 +1465,7 @@ class Backtesting: ].copy() if len(detail_data) == 0: # Fall back to "regular" data if no detail data was found for this candle - open_trade_count_start = self.backtest_loop( - row, pair, current_time, end_date, open_trade_count_start, trade_dir - ) + self.backtest_loop(row, pair, current_time, end_date, trade_dir) continue detail_data.loc[:, "enter_long"] = row[LONG_IDX] detail_data.loc[:, "exit_long"] = row[ELONG_IDX] @@ -1487,12 +1477,11 @@ class Backtesting: current_time_det = current_time for det_row in detail_data[HEADERS].values.tolist(): self.dataprovider._set_dataframe_max_date(current_time_det) - open_trade_count_start = self.backtest_loop( + self.backtest_loop( det_row, pair, current_time_det, end_date, - open_trade_count_start, trade_dir, is_first, ) @@ -1500,9 +1489,7 @@ class Backtesting: is_first = False else: self.dataprovider._set_dataframe_max_date(current_time) - open_trade_count_start = self.backtest_loop( - row, pair, current_time, end_date, open_trade_count_start, trade_dir - ) + self.backtest_loop(row, pair, current_time, end_date, trade_dir) self.handle_left_open(LocalTrade.bt_trades_open_pp, data=data) self.wallets.update() From 4882a18bf935b8539d7820a099d58321172a3fb0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 14:13:41 +0200 Subject: [PATCH 118/242] chore: add pair_detail test --- tests/optimize/test_backtesting.py | 133 ++++++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 9567a2cac..2bc4a8bce 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -16,7 +16,7 @@ from freqtrade.commands.optimize_commands import setup_optimize_configuration, s from freqtrade.configuration import TimeRange from freqtrade.data import history from freqtrade.data.btanalysis import BT_DATA_COLUMNS, evaluate_result_multi -from freqtrade.data.converter import clean_ohlcv_dataframe +from freqtrade.data.converter import clean_ohlcv_dataframe, ohlcv_fill_up_missing_data from freqtrade.data.dataprovider import DataProvider from freqtrade.data.history import get_timerange from freqtrade.enums import CandleType, ExitType, RunMode @@ -30,6 +30,7 @@ from freqtrade.util.datetime_helpers import dt_utc from tests.conftest import ( CURRENT_TEST_STRATEGY, EXMS, + generate_test_data, get_args, log_has, log_has_re, @@ -1560,6 +1561,136 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) assert len(evaluate_result_multi(results["results"], "5m", 1)) == 0 +@pytest.mark.parametrize("pair", ["ADA/USDT", "LTC/USDT"]) +@pytest.mark.parametrize("tres", [0, 20, 30]) +def test_backtest_multi_pair_detail( + default_conf_usdt, + fee, + mocker, + tres, + pair, +): + """ + literally the same as test_backtest_multi_pair - but with artificial data + and detail timeframe. + """ + + def _trend_alternate_hold(dataframe=None, metadata=None): + """ + Buy every xth candle - sell every other xth -2 (hold on to pairs a bit) + """ + if metadata["pair"] in ("ETH/USDT", "LTC/USDT"): + multi = 20 + else: + multi = 18 + dataframe["enter_long"] = np.where(dataframe.index % multi == 0, 1, 0) + dataframe["exit_long"] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0) + dataframe["enter_short"] = 0 + dataframe["exit_short"] = 0 + return dataframe + + default_conf_usdt["runmode"] = "backtest" + default_conf_usdt["stoploss"] = -1.0 + default_conf_usdt["minimal_roi"] = {"0": 100} + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) + mocker.patch(f"{EXMS}.get_fee", fee) + patch_exchange(mocker) + + raw_candles_1m = generate_test_data("1m", 2500, "2022-01-03 12:00:00+00:00") + raw_candles = ohlcv_fill_up_missing_data(raw_candles_1m, "5m", "dummy") + + pairs = ["ADA/USDT", "DASH/USDT", "ETH/USDT", "LTC/USDT", "NXT/USDT"] + data = {pair: raw_candles for pair in pairs} + + # Only use 500 lines to increase performance + data = trim_dictlist(data, -500) + + # Remove data for one pair from the beginning of the data + if tres > 0: + data[pair] = data[pair][tres:].reset_index() + default_conf_usdt["timeframe"] = "5m" + default_conf_usdt["max_open_trades"] = 3 + + backtesting = Backtesting(default_conf_usdt) + vr_spy = mocker.spy(backtesting, "validate_row") + backtesting._set_strategy(backtesting.strategylist[0]) + backtesting.strategy.bot_loop_start = MagicMock() + backtesting.strategy.advise_entry = _trend_alternate_hold # Override + backtesting.strategy.advise_exit = _trend_alternate_hold # Override + + processed = backtesting.strategy.advise_all_indicators(data) + min_date, max_date = get_timerange(processed) + + backtest_conf = { + "processed": deepcopy(processed), + "start_date": min_date, + "end_date": max_date, + } + + results = backtesting.backtest(**backtest_conf) + + # bot_loop_start is called once per candle. + assert backtesting.strategy.bot_loop_start.call_count == 499 + # Validated row once per candle and pair + assert vr_spy.call_count == 2495 + # List of calls pair args - in batches of 5 (s) + calls_per_candle = defaultdict(list) + for call in vr_spy.call_args_list: + calls_per_candle[call[0][3]].append(call[0][1]) + + all_orients = [x for _, x in calls_per_candle.items()] + + distinct_calls = [list(x) for x in set(tuple(x) for x in all_orients)] + + # All calls must be made for the full pairlist + assert all(len(x) == 5 for x in distinct_calls) + + # order varied - and is not always identical + assert not all( + x == ["ADA/USDT", "DASH/USDT", "ETH/USDT", "LTC/USDT", "NXT/USDT"] for x in distinct_calls + ) + # But some calls should've kept the original ordering + assert any( + x == ["ADA/USDT", "DASH/USDT", "ETH/USDT", "LTC/USDT", "NXT/USDT"] for x in distinct_calls + ) + assert ( + # Ordering can be different, but should be one of the following + any( + x == ["ETH/USDT", "ADA/USDT", "DASH/USDT", "LTC/USDT", "NXT/USDT"] + for x in distinct_calls + ) + or any( + x == ["ETH/USDT", "LTC/USDT", "ADA/USDT", "DASH/USDT", "NXT/USDT"] + for x in distinct_calls + ) + ) + + # Make sure we have parallel trades + assert len(evaluate_result_multi(results["results"], "5m", 2)) > 0 + # make sure we don't have trades with more than configured max_open_trades + assert len(evaluate_result_multi(results["results"], "5m", 3)) == 0 + + # Cached data correctly removed amounts + offset = 1 if tres == 0 else 0 + removed_candles = len(data[pair]) - offset + assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, "5m")[0]) == removed_candles + assert ( + len(backtesting.dataprovider.get_analyzed_dataframe("NXT/USDT", "5m")[0]) + == len(data["NXT/USDT"]) - 1 + ) + + backtesting.strategy.max_open_trades = 1 + backtesting.config.update({"max_open_trades": 1}) + backtest_conf = { + "processed": deepcopy(processed), + "start_date": min_date, + "end_date": max_date, + } + results = backtesting.backtest(**backtest_conf) + assert len(evaluate_result_multi(results["results"], "5m", 1)) == 0 + + def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): patch_exchange(mocker) mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest") From 530226dbe82a1f8c0ff95456afea589144a1ce17 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 14:19:44 +0200 Subject: [PATCH 119/242] chore: Add "use_detail" to detail test --- tests/optimize/test_backtesting.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 2bc4a8bce..8c1bce97c 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1561,6 +1561,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) assert len(evaluate_result_multi(results["results"], "5m", 1)) == 0 +@pytest.mark.parametrize("use_detail", [True, False]) @pytest.mark.parametrize("pair", ["ADA/USDT", "LTC/USDT"]) @pytest.mark.parametrize("tres", [0, 20, 30]) def test_backtest_multi_pair_detail( @@ -1569,6 +1570,7 @@ def test_backtest_multi_pair_detail( mocker, tres, pair, + use_detail, ): """ literally the same as test_backtest_multi_pair - but with artificial data @@ -1592,6 +1594,10 @@ def test_backtest_multi_pair_detail( default_conf_usdt["runmode"] = "backtest" default_conf_usdt["stoploss"] = -1.0 default_conf_usdt["minimal_roi"] = {"0": 100} + + if use_detail: + default_conf_usdt["timeframe_detail"] = "1m" + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) mocker.patch(f"{EXMS}.get_fee", fee) @@ -1602,6 +1608,7 @@ def test_backtest_multi_pair_detail( pairs = ["ADA/USDT", "DASH/USDT", "ETH/USDT", "LTC/USDT", "NXT/USDT"] data = {pair: raw_candles for pair in pairs} + detail_data = {pair: raw_candles_1m for pair in pairs} # Only use 500 lines to increase performance data = trim_dictlist(data, -500) @@ -1614,6 +1621,8 @@ def test_backtest_multi_pair_detail( backtesting = Backtesting(default_conf_usdt) vr_spy = mocker.spy(backtesting, "validate_row") + bl_spy = mocker.spy(backtesting, "backtest_loop") + backtesting.detail_data = detail_data backtesting._set_strategy(backtesting.strategylist[0]) backtesting.strategy.bot_loop_start = MagicMock() backtesting.strategy.advise_entry = _trend_alternate_hold # Override @@ -1634,6 +1643,15 @@ def test_backtest_multi_pair_detail( assert backtesting.strategy.bot_loop_start.call_count == 499 # Validated row once per candle and pair assert vr_spy.call_count == 2495 + + if use_detail: + # Backtest loop is called once per candle per pair + # Exact numbers depend on trade state - but should be around 3_800 + assert bl_spy.call_count > 3_800 + assert bl_spy.call_count < 3_900 + else: + assert bl_spy.call_count < 2495 + # List of calls pair args - in batches of 5 (s) calls_per_candle = defaultdict(list) for call in vr_spy.call_args_list: From 5773d1fd8d95c5eace2f2cb1d57ed1a80b0a7f99 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 14:40:19 +0200 Subject: [PATCH 120/242] docs: Update documentation for new flow --- docs/backtesting.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 5adeae54b..00a79592f 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -530,10 +530,9 @@ You can then load the trades to perform further analysis as shown in the [data a Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions: - Exchange [trading limits](#trading-limits-in-backtesting) are respected -- Entries happen at open-price +- Entries happen at open-price unless a custom price logic has been specified - All orders are filled at the requested price (no slippage) as long as the price is within the candle's high/low range - Exit-signal exits happen at open-price of the consecutive candle -- Exits don't free their trade slot for a new trade until the next candle - Exit-signal is favored over Stoploss, because exit-signals are assumed to trigger on candle's open - ROI - Exits are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the exit will be at 2%) From b727e5ca1c2628f7a99e380516d396d907464dc0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 14:43:54 +0200 Subject: [PATCH 121/242] chore: simplify update code --- tests/optimize/test_backtesting.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 8c1bce97c..c396e1135 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1591,9 +1591,13 @@ def test_backtest_multi_pair_detail( dataframe["exit_short"] = 0 return dataframe - default_conf_usdt["runmode"] = "backtest" - default_conf_usdt["stoploss"] = -1.0 - default_conf_usdt["minimal_roi"] = {"0": 100} + default_conf_usdt.update( + { + "runmode": "backtest", + "stoploss": -1.0, + "minimal_roi": {"0": 100}, + } + ) if use_detail: default_conf_usdt["timeframe_detail"] = "1m" From 50835c878e7ea50ded65f9aed2bf7ede9a1780fb Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 15:07:42 +0200 Subject: [PATCH 122/242] chore: add more test coverage --- tests/optimize/test_backtesting.py | 138 ++++++++++++++++++++++------- 1 file changed, 106 insertions(+), 32 deletions(-) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index c396e1135..5bc113a62 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1656,38 +1656,6 @@ def test_backtest_multi_pair_detail( else: assert bl_spy.call_count < 2495 - # List of calls pair args - in batches of 5 (s) - calls_per_candle = defaultdict(list) - for call in vr_spy.call_args_list: - calls_per_candle[call[0][3]].append(call[0][1]) - - all_orients = [x for _, x in calls_per_candle.items()] - - distinct_calls = [list(x) for x in set(tuple(x) for x in all_orients)] - - # All calls must be made for the full pairlist - assert all(len(x) == 5 for x in distinct_calls) - - # order varied - and is not always identical - assert not all( - x == ["ADA/USDT", "DASH/USDT", "ETH/USDT", "LTC/USDT", "NXT/USDT"] for x in distinct_calls - ) - # But some calls should've kept the original ordering - assert any( - x == ["ADA/USDT", "DASH/USDT", "ETH/USDT", "LTC/USDT", "NXT/USDT"] for x in distinct_calls - ) - assert ( - # Ordering can be different, but should be one of the following - any( - x == ["ETH/USDT", "ADA/USDT", "DASH/USDT", "LTC/USDT", "NXT/USDT"] - for x in distinct_calls - ) - or any( - x == ["ETH/USDT", "LTC/USDT", "ADA/USDT", "DASH/USDT", "NXT/USDT"] - for x in distinct_calls - ) - ) - # Make sure we have parallel trades assert len(evaluate_result_multi(results["results"], "5m", 2)) > 0 # make sure we don't have trades with more than configured max_open_trades @@ -1713,6 +1681,112 @@ def test_backtest_multi_pair_detail( assert len(evaluate_result_multi(results["results"], "5m", 1)) == 0 +@pytest.mark.parametrize("use_detail", [True, False]) +def test_backtest_multi_pair_long_short_switch( + default_conf_usdt, + fee, + mocker, + use_detail, +): + """ + literally the same as test_backtest_multi_pair - but with artificial data + and detail timeframe. + """ + + def _trend_alternate_hold(dataframe=None, metadata=None): + """ + Buy every xth candle - sell every other xth -2 (hold on to pairs a bit) + """ + if metadata["pair"] in ("ETH/USDT", "LTC/USDT"): + multi = 20 + else: + multi = 18 + dataframe["enter_long"] = np.where(dataframe.index % multi == 0, 1, 0) + dataframe["exit_long"] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0) + dataframe["enter_short"] = dataframe["exit_long"] + dataframe["exit_short"] = dataframe["enter_long"] + return dataframe + + default_conf_usdt.update( + { + "runmode": "backtest", + "timeframe": "5m", + "max_open_trades": 1, + "stoploss": -1.0, + "minimal_roi": {"0": 100}, + "margin_mode": "isolated", + "trading_mode": "futures", + } + ) + + if use_detail: + default_conf_usdt["timeframe_detail"] = "1m" + + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) + mocker.patch(f"{EXMS}.get_fee", fee) + patch_exchange(mocker) + + raw_candles_1m = generate_test_data("1m", 2500, "2022-01-03 12:00:00+00:00") + raw_candles = ohlcv_fill_up_missing_data(raw_candles_1m, "5m", "dummy") + + pairs = [ + "ETH/USDT:USDT", + ] + default_conf_usdt["exchange"]["pair_whitelist"] = pairs + # Fake whitelist to avoid some mock data issues + mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.01, 0.01)) + + data = {pair: raw_candles for pair in pairs} + detail_data = {pair: raw_candles_1m for pair in pairs} + + # Only use 500 lines to increase performance + data = trim_dictlist(data, -500) + + backtesting = Backtesting(default_conf_usdt) + vr_spy = mocker.spy(backtesting, "validate_row") + bl_spy = mocker.spy(backtesting, "backtest_loop") + backtesting.detail_data = detail_data + backtesting.funding_fee_timeframe_secs = 3600 * 8 # 8h + backtesting.futures_data = {pair: pd.DataFrame() for pair in pairs} + + backtesting.strategylist[0].can_short = True + backtesting._set_strategy(backtesting.strategylist[0]) + backtesting.strategy.bot_loop_start = MagicMock() + backtesting.strategy.advise_entry = _trend_alternate_hold # Override + backtesting.strategy.advise_exit = _trend_alternate_hold # Override + + processed = backtesting.strategy.advise_all_indicators(data) + min_date, max_date = get_timerange(processed) + + backtest_conf = { + "processed": deepcopy(processed), + "start_date": min_date, + "end_date": max_date, + } + + results = backtesting.backtest(**backtest_conf) + + # bot_loop_start is called once per candle. + assert backtesting.strategy.bot_loop_start.call_count == 499 + # Validated row once per candle and pair + assert vr_spy.call_count == 499 + + if use_detail: + # Backtest loop is called once per candle per pair + assert bl_spy.call_count == 1071 + else: + assert bl_spy.call_count == 479 + + # Make sure we have parallel trades + assert len(evaluate_result_multi(results["results"], "5m", 0)) > 0 + # make sure we don't have trades with more than configured max_open_trades + assert len(evaluate_result_multi(results["results"], "5m", 1)) == 0 + + # Expect 26 results initially + assert len(results["results"]) == 30 + + def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): patch_exchange(mocker) mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest") From 6cf92c2a906824e9a09316595118b351c697eb11 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 16:41:07 +0200 Subject: [PATCH 123/242] chore: enhanced aggregation syntax --- freqtrade/persistence/trade_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index eab4da4f3..e9ac98583 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -1217,7 +1217,7 @@ class LocalTrade: # with realized_profit. close_profit = (close_profit_abs / total_stake) * self.leverage else: - total_stake = total_stake + self._calc_open_trade_value(tmp_amount, price) + total_stake += self._calc_open_trade_value(tmp_amount, price) max_stake_amount += tmp_amount * price self.funding_fees = funding_fees self.max_stake_amount = float(max_stake_amount) From 7972a023ed73f73f4440d541ad79f3553a23ff6b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 16:45:51 +0200 Subject: [PATCH 124/242] fix: oddly wrong fee_cost calculation --- freqtrade/persistence/trade_model.py | 2 +- tests/persistence/test_persistence.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index e9ac98583..7ec781cdc 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -1236,7 +1236,7 @@ class LocalTrade: self.open_rate = float(current_stake / current_amount) self.amount = current_amount_tr self.stake_amount = float(current_stake) / (self.leverage or 1.0) - self.fee_open_cost = self.fee_open * float(current_stake) + self.fee_open_cost = self.fee_open * float(self.max_stake_amount) self.recalc_open_trade_value() if self.stop_loss_pct is not None and self.open_rate is not None: self.adjust_stop_loss(self.open_rate, self.stop_loss_pct) diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index 7ebaf715c..e50f1f8ce 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -2571,7 +2571,7 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short): assert trade.amount == 2 * o1_amount assert trade.stake_amount == 2 * o1_amount assert trade.open_rate == o1_rate - assert trade.fee_open_cost == 2 * o1_fee_cost + assert trade.fee_open_cost == trade.nr_of_successful_entries * o1_fee_cost assert trade.open_trade_value == 2 * o1_trade_val assert trade.nr_of_successful_entries == 2 @@ -2598,7 +2598,7 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short): assert trade.amount == o1_amount assert trade.stake_amount == o1_amount assert trade.open_rate == o1_rate - assert trade.fee_open_cost == o1_fee_cost + assert trade.fee_open_cost == trade.nr_of_successful_entries * o1_fee_cost assert trade.open_trade_value == o1_trade_val assert trade.nr_of_successful_entries == 2 @@ -2626,7 +2626,7 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short): assert trade.amount == 2 * o1_amount assert trade.stake_amount == 2 * o1_amount assert trade.open_rate == o1_rate - assert trade.fee_open_cost == 2 * o1_fee_cost + assert trade.fee_open_cost == trade.nr_of_successful_entries * o1_fee_cost assert trade.open_trade_value == 2 * o1_trade_val assert trade.nr_of_successful_entries == 3 From 5cb6c234c4d3145989570aeccdfedfc44091c553 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 19:43:48 +0200 Subject: [PATCH 125/242] chore: improve naming for refresh_latest_trades --- freqtrade/exchange/exchange.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 197e41cb2..f2ad86ec6 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2787,16 +2787,16 @@ class Exchange: ) logger.debug("Refreshing TRADES data for %d pairs", len(pair_list)) results_df = {} - coros = [] - for pairwt in set(pair_list): - coros.append(self._build_trades_dl_jobs(pairwt, data_handler, cache)) + trades_dl_jobs = [] + for pair_wt in set(pair_list): + trades_dl_jobs.append(self._build_trades_dl_jobs(pair_wt, data_handler, cache)) - async def gather_stuff(coro): + async def gather_coroutines(coro): return await asyncio.gather(*coro, return_exceptions=True) - for input_coro in chunks(coros, 100): + for dl_job_chunk in chunks(trades_dl_jobs, 100): with self._loop_lock: - results = self.loop.run_until_complete(gather_stuff(input_coro)) + results = self.loop.run_until_complete(gather_coroutines(dl_job_chunk)) for res in results: if isinstance(res, Exception): From 784208dd87c666c0a289ec83be84882aad938323 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 19:44:35 +0200 Subject: [PATCH 126/242] chore: improve variable naming for ohlcv --- freqtrade/exchange/exchange.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index f2ad86ec6..f4f0266b1 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2470,17 +2470,17 @@ class Exchange: logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list)) # Gather coroutines to run - input_coroutines, cached_pairs = self._build_ohlcv_dl_jobs(pair_list, since_ms, cache) + ohlcv_dl_jobs, cached_pairs = self._build_ohlcv_dl_jobs(pair_list, since_ms, cache) results_df = {} # Chunk requests into batches of 100 to avoid overwhelming ccxt Throttling - for input_coro in chunks(input_coroutines, 100): + for dl_jobs_batch in chunks(ohlcv_dl_jobs, 100): - async def gather_stuff(coro): + async def gather_coroutines(coro): return await asyncio.gather(*coro, return_exceptions=True) with self._loop_lock: - results = self.loop.run_until_complete(gather_stuff(input_coro)) + results = self.loop.run_until_complete(gather_coroutines(dl_jobs_batch)) for res in results: if isinstance(res, Exception): From b63c04df4f5ae3762d4e1e653f83d3c7df1a159c Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2024 20:09:22 +0200 Subject: [PATCH 127/242] chore: update help wording --- freqtrade/commands/arguments.py | 2 +- freqtrade/commands/data_commands.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 0c93af78a..c76ee3fc6 100755 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -457,7 +457,7 @@ class Arguments: # Add list-data subcommand list_data_cmd = subparsers.add_parser( "list-data", - help="List downloaded data.", + help="List downloaded OHLCV data.", parents=[_common_parser], ) list_data_cmd.set_defaults(func=start_list_data) diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index f3f56c7b2..344b97076 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -115,7 +115,7 @@ def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None: def start_list_data(args: Dict[str, Any]) -> None: """ - List available backtest data + List available OHLCV data """ config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) From 263be72c11b5c86d4894b847a2675b2ae9da1030 Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Tue, 13 Aug 2024 03:02:59 +0000 Subject: [PATCH 128/242] chore: update pre-commit hooks --- .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 2e3a2c182..7bf5de208 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.5.6' + rev: 'v0.5.7' hooks: - id: ruff From cf26635e3cf3bfc52d3b679c2293112afc1a8112 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 06:36:39 +0200 Subject: [PATCH 129/242] feat: add trades helper functions trades_get_available data and trades_data_min_max --- .../data/history/datahandlers/idatahandler.py | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/history/datahandlers/idatahandler.py b/freqtrade/data/history/datahandlers/idatahandler.py index aea7ea14a..db1660dc8 100644 --- a/freqtrade/data/history/datahandlers/idatahandler.py +++ b/freqtrade/data/history/datahandlers/idatahandler.py @@ -12,7 +12,7 @@ from datetime import datetime, timezone from pathlib import Path from typing import List, Optional, Tuple, Type -from pandas import DataFrame +from pandas import DataFrame, to_datetime from freqtrade import misc from freqtrade.configuration import TimeRange @@ -32,6 +32,7 @@ logger = logging.getLogger(__name__) class IDataHandler(ABC): _OHLCV_REGEX = r"^([a-zA-Z_\d-]+)\-(\d+[a-zA-Z]{1,2})\-?([a-zA-Z_]*)?(?=\.)" + _TRADES_REGEX = r"^([a-zA-Z_\d-]+)\-(trades)?(?=\.)" def __init__(self, datadir: Path) -> None: self._datadir = datadir @@ -166,6 +167,50 @@ class IDataHandler(ABC): :param candle_type: Any of the enum CandleType (must match trading mode!) """ + @classmethod + def trades_get_available_data(cls, datadir: Path, trading_mode: TradingMode) -> List[str]: + """ + Returns a list of all pairs with ohlcv data available in this datadir + :param datadir: Directory to search for ohlcv files + :param trading_mode: trading-mode to be used + :return: List of Tuples of (pair, timeframe, CandleType) + """ + if trading_mode == TradingMode.FUTURES: + datadir = datadir.joinpath("futures") + _tmp = [ + re.search(cls._TRADES_REGEX, p.name) + for p in datadir.glob(f"*.{cls._get_file_extension()}") + ] + return [ + cls.rebuild_pair_from_filename(match[1]) + for match in _tmp + if match and len(match.groups()) > 1 + ] + + def trades_data_min_max( + self, + pair: str, + trading_mode: TradingMode, + ) -> Tuple[datetime, datetime, int]: + """ + Returns the min and max timestamp for the given pair's trades data. + :param pair: Pair to get min/max for + :param trading_mode: Trading mode to use (used to determine the filename) + :return: (min, max, len) + """ + df = self._trades_load(pair, trading_mode) + if df.empty: + return ( + datetime.fromtimestamp(0, tz=timezone.utc), + datetime.fromtimestamp(0, tz=timezone.utc), + 0, + ) + return ( + to_datetime(df.iloc[0]["timestamp"], unit="ms", utc=True).to_pydatetime(), + to_datetime(df.iloc[-1]["timestamp"], unit="ms", utc=True).to_pydatetime(), + len(df), + ) + @classmethod def trades_get_pairs(cls, datadir: Path) -> List[str]: """ From 3f4c19abbcda40e3bbc16bbe90a2fac41d23f853 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 06:40:11 +0200 Subject: [PATCH 130/242] chore: add test for trades_get_available_data --- tests/data/test_datahandler.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/data/test_datahandler.py b/tests/data/test_datahandler.py index b8bb5661f..38f658dfb 100644 --- a/tests/data/test_datahandler.py +++ b/tests/data/test_datahandler.py @@ -519,6 +519,21 @@ def test_datahandler_trades_purge(mocker, testdatadir, datahandler): assert unlinkmock.call_count == 1 +def test_datahandler_trades_get_available_data(testdatadir): + paircombs = FeatherDataHandler.trades_get_available_data(testdatadir, TradingMode.SPOT) + # Convert to set to avoid failures due to sorting + assert set(paircombs) == {"XRP/ETH"} + + paircombs = FeatherDataHandler.trades_get_available_data(testdatadir, TradingMode.FUTURES) + # Convert to set to avoid failures due to sorting + assert set(paircombs) == set() + + paircombs = JsonGzDataHandler.trades_get_available_data(testdatadir, TradingMode.SPOT) + assert set(paircombs) == {"XRP/ETH", "XRP/OLD"} + paircombs = HDF5DataHandler.trades_get_available_data(testdatadir, TradingMode.SPOT) + assert set(paircombs) == {"XRP/ETH"} + + def test_gethandlerclass(): cl = get_datahandlerclass("json") assert cl == JsonDataHandler From 9bfd0cb63cdcac421b97bf6861815f542c136f70 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 06:43:26 +0200 Subject: [PATCH 131/242] feat: add test for trades_data_minmax --- tests/data/test_datahandler.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/data/test_datahandler.py b/tests/data/test_datahandler.py index 38f658dfb..99af63eca 100644 --- a/tests/data/test_datahandler.py +++ b/tests/data/test_datahandler.py @@ -534,6 +534,24 @@ def test_datahandler_trades_get_available_data(testdatadir): assert set(paircombs) == {"XRP/ETH"} +def test_datahandler_trades_data_min_max(testdatadir): + dh = FeatherDataHandler(testdatadir) + min_max = dh.trades_data_min_max("XRP/ETH", TradingMode.SPOT) + assert len(min_max) == 3 + + # Empty pair + min_max = dh.trades_data_min_max("NADA/ETH", TradingMode.SPOT) + assert len(min_max) == 3 + assert min_max[0] == datetime.fromtimestamp(0, tz=timezone.utc) + assert min_max[0] == min_max[1] + + # Existing pair ... + min_max = dh.trades_data_min_max("XRP/ETH", TradingMode.SPOT) + assert len(min_max) == 3 + assert min_max[0] == datetime(2019, 10, 11, 0, 0, 11, 620000, tzinfo=timezone.utc) + assert min_max[1] == datetime(2019, 10, 13, 11, 19, 28, 844000, tzinfo=timezone.utc) + + def test_gethandlerclass(): cl = get_datahandlerclass("json") assert cl == JsonDataHandler From 5a9f87ac6354b17d8d93bf340a67cd3211cc3d78 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 06:44:28 +0200 Subject: [PATCH 132/242] feat: add start_list_trades_data command to output trades data --- freqtrade/commands/__init__.py | 1 + freqtrade/commands/data_commands.py | 53 ++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/freqtrade/commands/__init__.py b/freqtrade/commands/__init__.py index 48ee18e93..ec145eb5f 100644 --- a/freqtrade/commands/__init__.py +++ b/freqtrade/commands/__init__.py @@ -15,6 +15,7 @@ from freqtrade.commands.data_commands import ( start_convert_trades, start_download_data, start_list_data, + start_list_trades_data, ) from freqtrade.commands.db_commands import start_convert_db from freqtrade.commands.deploy_commands import ( diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index 344b97076..ba5f3563a 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -118,6 +118,10 @@ def start_list_data(args: Dict[str, Any]) -> None: List available OHLCV data """ + if args["download_trades"]: + start_list_trades_data(args) + return + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) from freqtrade.data.history import get_datahandler @@ -127,7 +131,6 @@ def start_list_data(args: Dict[str, Any]) -> None: paircombs = dhc.ohlcv_get_available_data( config["datadir"], config.get("trading_mode", TradingMode.SPOT) ) - if args["pairs"]: paircombs = [comb for comb in paircombs if comb[0] in args["pairs"]] title = f"Found {len(paircombs)} pair / timeframe combinations." @@ -171,3 +174,51 @@ def start_list_data(args: Dict[str, Any]) -> None: summary=title, table_kwargs={"min_width": 50}, ) + + +def start_list_trades_data(args: Dict[str, Any]) -> None: + """ + List available Trades data + """ + + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + + from freqtrade.data.history import get_datahandler + + dhc = get_datahandler(config["datadir"], config["dataformat_trades"]) + + paircombs = dhc.trades_get_available_data( + config["datadir"], config.get("trading_mode", TradingMode.SPOT) + ) + + if args["pairs"]: + paircombs = [comb for comb in paircombs if comb in args["pairs"]] + + title = f"Found trades data for {len(paircombs)} pairs." + if not config.get("show_timerange"): + print_rich_table( + [(pair, config.get("candle_type_def", CandleType.SPOT)) for pair in sorted(paircombs)], + ("Pair", "Type"), + title, + table_kwargs={"min_width": 50}, + ) + else: + paircombs1 = [ + (pair, *dhc.trades_data_min_max(pair, config.get("trading_mode", TradingMode.SPOT))) + for pair in paircombs + ] + print_rich_table( + [ + ( + pair, + config.get("candle_type_def", CandleType.SPOT), + start.strftime(DATETIME_PRINT_FORMAT), + end.strftime(DATETIME_PRINT_FORMAT), + str(length), + ) + for pair, start, end, length in sorted(paircombs1, key=lambda x: (x[0])) + ], + ("Pair", "Type", "From", "To", "Trades"), + summary=title, + table_kwargs={"min_width": 50}, + ) From d02ea3244a0a13eb0ca236b18dcee4f0f6bf0e63 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 06:46:02 +0200 Subject: [PATCH 133/242] feat: add "trades" switch to list-data command --- freqtrade/commands/arguments.py | 10 +++++++++- freqtrade/commands/cli_options.py | 8 ++++++-- freqtrade/commands/data_commands.py | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index c76ee3fc6..56e24f79f 100755 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -132,7 +132,15 @@ ARGS_CONVERT_TRADES = [ "trading_mode", ] -ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs", "trading_mode", "show_timerange"] +ARGS_LIST_DATA = [ + "exchange", + "dataformat_ohlcv", + "dataformat_trades", + "trades", + "pairs", + "trading_mode", + "show_timerange", +] ARGS_DOWNLOAD_DATA = [ "pairs", diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index b9236a0ab..54e139443 100755 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -446,8 +446,12 @@ AVAILABLE_CLI_OPTIONS = { ), "download_trades": Arg( "--dl-trades", - help="Download trades instead of OHLCV data. The bot will resample trades to the " - "desired timeframe as specified as --timeframes/-t.", + help="Download trades instead of OHLCV data.", + action="store_true", + ), + "trades": Arg( + "--trades", + help="Work on trades data instead of OHLCV data.", action="store_true", ), "convert_trades": Arg( diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index ba5f3563a..ac150efa6 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -118,7 +118,7 @@ def start_list_data(args: Dict[str, Any]) -> None: List available OHLCV data """ - if args["download_trades"]: + if args["trades"]: start_list_trades_data(args) return From a991c768422a60ad9ce12778b23831ada6be455a Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 06:54:35 +0200 Subject: [PATCH 134/242] feat: add test for test_list_data command --- freqtrade/commands/data_commands.py | 3 +- tests/commands/test_commands.py | 47 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index ac150efa6..4932ffd3f 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -14,6 +14,7 @@ from freqtrade.data.history import download_data_main from freqtrade.enums import CandleType, RunMode, TradingMode from freqtrade.exceptions import ConfigurationError from freqtrade.exchange import timeframe_to_minutes +from freqtrade.misc import plural from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist from freqtrade.resolvers import ExchangeResolver from freqtrade.util import print_rich_table @@ -194,7 +195,7 @@ def start_list_trades_data(args: Dict[str, Any]) -> None: if args["pairs"]: paircombs = [comb for comb in paircombs if comb in args["pairs"]] - title = f"Found trades data for {len(paircombs)} pairs." + title = f"Found trades data for {len(paircombs)} {plural(len(paircombs), "pair")}." if not config.get("show_timerange"): print_rich_table( [(pair, config.get("candle_type_def", CandleType.SPOT)) for pair in sorted(paircombs)], diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 687bff69f..5332014d2 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -1692,6 +1692,53 @@ def test_start_list_data(testdatadir, capsys): ) +def test_start_list_trades_data(testdatadir, capsys): + args = [ + "list-data", + "--datadir", + str(testdatadir), + "--trades", + ] + pargs = get_args(args) + pargs["config"] = None + start_list_data(pargs) + captured = capsys.readouterr() + assert "Found trades data for 1 pair." in captured.out + assert re.search(r".*Pair.*Type.*\n", captured.out) + assert re.search(r"\n.* XRP/ETH .* spot |\n", captured.out) + + args = [ + "list-data", + "--datadir", + str(testdatadir), + "--trades", + "--show-timerange", + ] + pargs = get_args(args) + pargs["config"] = None + start_list_data(pargs) + captured = capsys.readouterr() + assert "Found trades data for 1 pair." in captured.out + assert re.search(r".*Pair.*Type.*From.*To.*Trades.*\n", captured.out) + assert re.search( + r"\n.* XRP/ETH .* spot .* 2019-10-11 00:00:01 .* 2019-10-13 11:19:28 .* 12477 .*|\n", + captured.out, + ) + + args = [ + "list-data", + "--datadir", + str(testdatadir), + "--trades", + "--show-timerange", + ] + pargs = get_args(args) + pargs["config"] = None + start_list_data(pargs) + captured = capsys.readouterr() + assert "Found trades data for 0 pair." in captured.out + + @pytest.mark.usefixtures("init_persistence") def test_show_trades(mocker, fee, capsys, caplog): mocker.patch("freqtrade.persistence.init_db") From f009625c1a07ffc8085bf8c9b39a47bc127b80c1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 07:01:23 +0200 Subject: [PATCH 135/242] docs: update list-data documentation --- docs/data-download.md | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/docs/data-download.md b/docs/data-download.md index 2a51edb0b..3bf831280 100644 --- a/docs/data-download.md +++ b/docs/data-download.md @@ -423,7 +423,8 @@ You can get a list of downloaded data using the `list-data` sub-command. usage: freqtrade list-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--exchange EXCHANGE] [--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}] - [-p PAIRS [PAIRS ...]] + [--data-format-trades {json,jsongz,hdf5,feather,parquet}] + [--trades] [-p PAIRS [PAIRS ...]] [--trading-mode {spot,margin,futures}] [--show-timerange] @@ -433,6 +434,10 @@ options: --data-format-ohlcv {json,jsongz,hdf5,feather,parquet} Storage format for downloaded candle (OHLCV) data. (default: `feather`). + --data-format-trades {json,jsongz,hdf5,feather,parquet} + Storage format for downloaded trades data. (default: + `feather`). + --trades Work on trades data instead of OHLCV data. -p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...] Limit command to these pairs. Pairs are space- separated. @@ -465,13 +470,28 @@ Common arguments: ```bash > freqtrade list-data --userdir ~/.freqtrade/user_data/ -Found 33 pair / timeframe combinations. -pairs timeframe ----------- ----------------------------------------- -ADA/BTC 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d -ADA/ETH 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d -ETH/BTC 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d -ETH/USDT 5m, 15m, 30m, 1h, 2h, 4h + Found 33 pair / timeframe combinations. +┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┓ +┃ Pair ┃ Timeframe ┃ Type ┃ +┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━┩ +│ ADA/BTC │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │ +│ ADA/ETH │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │ +│ ETH/BTC │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │ +│ ETH/USDT │ 5m, 15m, 30m, 1h, 2h, 4h │ spot │ +└────────────────┴──────────────────────────────────────────┴──────┘ + +``` + +Show all trades data including from/to timerange +``` bash +> freqtrade list-data --show --trades + Found trades data for 1 pair. +┏━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┓ +┃ Pair ┃ Type ┃ From ┃ To ┃ Trades ┃ +┡━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━┩ +│ XRP/ETH │ spot │ 2019-10-11 00:00:11 │ 2019-10-13 11:19:28 │ 12477 │ +└─────────┴──────┴─────────────────────┴─────────────────────┴────────┘ + ``` ## Trades (tick) data From 2b86865b9b78ef3029dc86f1969eca36beb25ef9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 07:02:53 +0200 Subject: [PATCH 136/242] chore: improve wording in subcommand helptext --- freqtrade/commands/arguments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 56e24f79f..62a79b0e8 100755 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -465,7 +465,7 @@ class Arguments: # Add list-data subcommand list_data_cmd = subparsers.add_parser( "list-data", - help="List downloaded OHLCV data.", + help="List downloaded data.", parents=[_common_parser], ) list_data_cmd.set_defaults(func=start_list_data) From ef04324f9d079a2ec0e26f8905c3054047fb21b8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 07:03:00 +0200 Subject: [PATCH 137/242] docs: update --help output docs --- README.md | 17 +++++++++++++---- docs/bot-usage.md | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d7ab7c05c..137078214 100644 --- a/README.md +++ b/README.md @@ -86,41 +86,50 @@ For further (native) installation methods, please refer to the [Installation doc ``` usage: freqtrade [-h] [-V] - {trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver} + {trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis} ... Free, open source crypto trading bot positional arguments: - {trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver} + {trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis} trade Trade module. create-userdir Create user-data directory. new-config Create new config + show-config Show resolved config new-strategy Create new strategy download-data Download backtesting data. convert-data Convert candle (OHLCV) data from one format to another. convert-trade-data Convert trade data from one format to another. + trades-to-ohlcv Convert trade data to OHLCV data. list-data List downloaded data. backtesting Backtesting module. + backtesting-show Show past Backtest results + backtesting-analysis + Backtest Analysis module. edge Edge module. hyperopt Hyperopt module. hyperopt-list List Hyperopt results hyperopt-show Show details of Hyperopt results list-exchanges Print available exchanges. - list-hyperopts Print available hyperopt classes. list-markets Print markets on exchange. list-pairs Print pairs on exchange. list-strategies Print available strategies. + list-freqaimodels Print available freqAI models. list-timeframes Print available timeframes for the exchange. show-trades Show trades. test-pairlist Test your pairlist configuration. + convert-db Migrate database to different system install-ui Install FreqUI plot-dataframe Plot candles with indicators. plot-profit Generate plot showing profits. webserver Webserver module. + strategy-updater updates outdated strategy files to the current version + lookahead-analysis Check for potential look ahead bias. + recursive-analysis Check for potential recursive formula issue. -optional arguments: +options: -h, --help show this help message and exit -V, --version show program's version number and exit diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 7aeda0c42..8ebc82552 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -12,41 +12,50 @@ This page explains the different parameters of the bot and how to run it. ``` usage: freqtrade [-h] [-V] - {trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver} + {trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis} ... Free, open source crypto trading bot positional arguments: - {trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver} + {trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis} trade Trade module. create-userdir Create user-data directory. new-config Create new config + show-config Show resolved config new-strategy Create new strategy download-data Download backtesting data. convert-data Convert candle (OHLCV) data from one format to another. convert-trade-data Convert trade data from one format to another. + trades-to-ohlcv Convert trade data to OHLCV data. list-data List downloaded data. backtesting Backtesting module. + backtesting-show Show past Backtest results + backtesting-analysis + Backtest Analysis module. edge Edge module. hyperopt Hyperopt module. hyperopt-list List Hyperopt results hyperopt-show Show details of Hyperopt results list-exchanges Print available exchanges. - list-hyperopts Print available hyperopt classes. list-markets Print markets on exchange. list-pairs Print pairs on exchange. list-strategies Print available strategies. + list-freqaimodels Print available freqAI models. list-timeframes Print available timeframes for the exchange. show-trades Show trades. test-pairlist Test your pairlist configuration. + convert-db Migrate database to different system install-ui Install FreqUI plot-dataframe Plot candles with indicators. plot-profit Generate plot showing profits. webserver Webserver module. + strategy-updater updates outdated strategy files to the current version + lookahead-analysis Check for potential look ahead bias. + recursive-analysis Check for potential recursive formula issue. -optional arguments: +options: -h, --help show this help message and exit -V, --version show program's version number and exit From c7bc1b10e29c56c84bf049cf25d2457cc8629c0f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 07:04:51 +0200 Subject: [PATCH 138/242] docs: fix messed up formatting --- docs/data-download.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/data-download.md b/docs/data-download.md index 3bf831280..59fa23b97 100644 --- a/docs/data-download.md +++ b/docs/data-download.md @@ -474,15 +474,16 @@ Common arguments: ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┓ ┃ Pair ┃ Timeframe ┃ Type ┃ ┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━┩ -│ ADA/BTC │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │ -│ ADA/ETH │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │ -│ ETH/BTC │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │ -│ ETH/USDT │ 5m, 15m, 30m, 1h, 2h, 4h │ spot │ -└────────────────┴──────────────────────────────────────────┴──────┘ +│ ADA/BTC │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │ +│ ADA/ETH │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │ +│ ETH/BTC │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │ +│ ETH/USDT │ 5m, 15m, 30m, 1h, 2h, 4h │ spot │ +└───────────────┴───────────────────────────────────────────┴──────┘ ``` Show all trades data including from/to timerange + ``` bash > freqtrade list-data --show --trades Found trades data for 1 pair. From 0f820e449829295efd3fc1a68209e565ea748506 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 07:12:57 +0200 Subject: [PATCH 139/242] chore: Fix 3.9 syntax error --- freqtrade/commands/data_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index 4932ffd3f..a114444b3 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -195,7 +195,7 @@ def start_list_trades_data(args: Dict[str, Any]) -> None: if args["pairs"]: paircombs = [comb for comb in paircombs if comb in args["pairs"]] - title = f"Found trades data for {len(paircombs)} {plural(len(paircombs), "pair")}." + title = f"Found trades data for {len(paircombs)} {plural(len(paircombs), 'pair')}." if not config.get("show_timerange"): print_rich_table( [(pair, config.get("candle_type_def", CandleType.SPOT)) for pair in sorted(paircombs)], From 948e67a2b741e1563a7e5392d489bf65196944e2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 07:14:08 +0200 Subject: [PATCH 140/242] docs: improved wording --- docs/backtesting.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/backtesting.md b/docs/backtesting.md index 00a79592f..2feba7ada 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -533,6 +533,7 @@ Since backtesting lacks some detailed information about what happens within a ca - Entries happen at open-price unless a custom price logic has been specified - All orders are filled at the requested price (no slippage) as long as the price is within the candle's high/low range - Exit-signal exits happen at open-price of the consecutive candle +- Exits free their trade slot for a new trade with a different pair - Exit-signal is favored over Stoploss, because exit-signals are assumed to trigger on candle's open - ROI - Exits are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the exit will be at 2%) From 1e410feed1515fb984ce72f953ad108f354bd292 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 07:33:13 +0200 Subject: [PATCH 141/242] test: fix missing test arg --- tests/commands/test_commands.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 5332014d2..c55126db1 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -1730,13 +1730,14 @@ def test_start_list_trades_data(testdatadir, capsys): "--datadir", str(testdatadir), "--trades", - "--show-timerange", + "--trading-mode", + "futures", ] pargs = get_args(args) pargs["config"] = None start_list_data(pargs) captured = capsys.readouterr() - assert "Found trades data for 0 pair." in captured.out + assert "Found trades data for 0 pairs." in captured.out @pytest.mark.usefixtures("init_persistence") From f8de46cea90cf12eeafb1aa60b0abc847bdf8b3b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 09:09:12 +0200 Subject: [PATCH 142/242] feat: Add precision_mode_price column --- freqtrade/persistence/migrations.py | 13 ++++++++----- freqtrade/persistence/trade_model.py | 8 ++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index e2e3b2175..2150d76bc 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -147,6 +147,9 @@ def migrate_trades_and_orders_table( price_precision = get_column_def(cols, "price_precision", "null") precision_mode = get_column_def(cols, "precision_mode", "null") contract_size = get_column_def(cols, "contract_size", "null") + precision_mode_price = get_column_def( + cols, "precision_mode_price", get_column_def(cols, "precision_mode", "null") + ) # Schema migration necessary with engine.begin() as connection: @@ -177,7 +180,7 @@ def migrate_trades_and_orders_table( timeframe, open_trade_value, close_profit_abs, trading_mode, leverage, liquidation_price, is_short, interest_rate, funding_fees, funding_fee_running, realized_profit, - amount_precision, price_precision, precision_mode, contract_size, + amount_precision, price_precision, precision_mode, precision_mode_price, contract_size, max_stake_amount ) select id, lower(exchange), pair, {base_currency} base_currency, @@ -207,8 +210,8 @@ def migrate_trades_and_orders_table( {funding_fees} funding_fees, {funding_fee_running} funding_fee_running, {realized_profit} realized_profit, {amount_precision} amount_precision, {price_precision} price_precision, - {precision_mode} precision_mode, {contract_size} contract_size, - {max_stake_amount} max_stake_amount + {precision_mode} precision_mode, {precision_mode_price} precision_mode_price, + {contract_size} contract_size, {max_stake_amount} max_stake_amount from {trade_back_name} """ ) @@ -348,8 +351,8 @@ def check_migrate(engine, decl_base, previous_tables) -> None: # if ('orders' not in previous_tables # or not has_column(cols_orders, 'funding_fee')): migrating = False - # if not has_column(cols_trades, 'funding_fee_running'): - if not has_column(cols_orders, "ft_order_tag"): + if not has_column(cols_trades, "precision_mode_price"): + # if not has_column(cols_orders, "ft_order_tag"): migrating = True logger.info( f"Running database migration for trades - " diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 7ec781cdc..67f5b37f1 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -433,6 +433,7 @@ class LocalTrade: amount_precision: Optional[float] = None price_precision: Optional[float] = None precision_mode: Optional[int] = None + precision_mode_price: Optional[int] = None contract_size: Optional[float] = None # Leverage trading properties @@ -730,6 +731,7 @@ class LocalTrade: "amount_precision": self.amount_precision, "price_precision": self.price_precision, "precision_mode": self.precision_mode, + "precision_mode_price": self.precision_mode_price, "contract_size": self.contract_size, "has_open_orders": self.has_open_orders, "orders": orders_json, @@ -810,7 +812,7 @@ class LocalTrade: stop_loss_norm = price_to_precision( new_loss, self.price_precision, - self.precision_mode, + self.precision_mode_price, rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP, ) # no stop loss assigned yet @@ -819,7 +821,7 @@ class LocalTrade: self.initial_stop_loss = price_to_precision( stop_loss_norm, self.price_precision, - self.precision_mode, + self.precision_mode_price, rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP, ) self.initial_stop_loss_pct = -1 * abs(stoploss) @@ -1562,6 +1564,7 @@ class LocalTrade: amount_precision=data.get("amount_precision", None), price_precision=data.get("price_precision", None), precision_mode=data.get("precision_mode", None), + precision_mode_price=data.get("precision_mode_price", data.get("precision_mode", None)), contract_size=data.get("contract_size", None), ) for order in data["orders"]: @@ -1695,6 +1698,7 @@ class Trade(ModelBase, LocalTrade): ) price_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore precision_mode: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore + precision_mode_price: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore contract_size: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore # Leverage trading properties From 54bc60b08f617e765ab0aa456718bdf920f4378e Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 09:10:50 +0200 Subject: [PATCH 143/242] test: Update test for new to-json field --- tests/persistence/test_persistence.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index e50f1f8ce..05160c74a 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -1404,6 +1404,7 @@ def test_to_json(fee): exchange="binance", enter_tag=None, precision_mode=1, + precision_mode_price=1, amount_precision=8.0, price_precision=7.0, contract_size=1, @@ -1473,6 +1474,7 @@ def test_to_json(fee): "amount_precision": 8.0, "price_precision": 7.0, "precision_mode": 1, + "precision_mode_price": 1, "contract_size": 1, "orders": [], "has_open_orders": False, @@ -1493,6 +1495,7 @@ def test_to_json(fee): enter_tag="buys_signal_001", exchange="binance", precision_mode=2, + precision_mode_price=1, amount_precision=7.0, price_precision=8.0, contract_size=1, @@ -1562,6 +1565,7 @@ def test_to_json(fee): "amount_precision": 7.0, "price_precision": 8.0, "precision_mode": 2, + "precision_mode_price": 1, "contract_size": 1, "orders": [], "has_open_orders": False, From ac1ac0debedf96b3c9b62cb5c24d14f86e64ec18 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 09:11:44 +0200 Subject: [PATCH 144/242] feat: set precision_mode_price when creating trade objects --- freqtrade/exchange/exchange.py | 5 +++++ freqtrade/freqtradebot.py | 2 ++ freqtrade/optimize/backtesting.py | 8 ++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 631f5587d..dc9c1d0bb 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -419,6 +419,11 @@ class Exchange: """exchange ccxt precisionMode""" return self._api.precisionMode + @property + def precision_mode_price(self) -> int: + """exchange ccxt precisionMode""" + return self._api.precisionMode + def additional_exchange_init(self) -> None: """ Additional exchange initialization logic. diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5a33e9fa6..c970e440b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -374,6 +374,7 @@ class FreqtradeBot(LoggingMixin): if trade.exchange != self.exchange.id: continue trade.precision_mode = self.exchange.precisionMode + trade.precision_mode_price = self.exchange.precision_mode_price trade.amount_precision = self.exchange.get_precision_amount(trade.pair) trade.price_precision = self.exchange.get_precision_price(trade.pair) trade.contract_size = self.exchange.get_contract_size(trade.pair) @@ -992,6 +993,7 @@ class FreqtradeBot(LoggingMixin): amount_precision=self.exchange.get_precision_amount(pair), price_precision=self.exchange.get_precision_price(pair), precision_mode=self.exchange.precisionMode, + precision_mode_price=self.exchange.precision_mode_price, contract_size=self.exchange.get_contract_size(pair), ) stoploss = self.strategy.stoploss if not self.edge else self.edge.get_stoploss(pair) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index c9bdf4c65..f02b1b002 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -181,6 +181,7 @@ class Backtesting: self.fee = max(fee for fee in fees if fee is not None) logger.info(f"Using fee {self.fee:.4%} - worst case fee from exchange (lowest tier).") self.precision_mode = self.exchange.precisionMode + self.precision_mode_price = self.exchange.precision_mode_price if self.config.get("freqai_backtest_live_models", False): from freqtrade.freqai.utils import get_timerange_backtest_live_models @@ -785,7 +786,7 @@ class Backtesting: ) if rate is not None and rate != close_rate: close_rate = price_to_precision( - rate, trade.price_precision, self.precision_mode + rate, trade.price_precision, self.precision_mode_price ) # We can't place orders lower than current low. # freqtrade does not support this in live, and the order would fill immediately @@ -929,7 +930,9 @@ class Backtesting: # We can't place orders higher than current high (otherwise it'd be a stop limit entry) # which freqtrade does not support in live. if new_rate is not None and new_rate != propose_rate: - propose_rate = price_to_precision(new_rate, price_precision, self.precision_mode) + propose_rate = price_to_precision( + new_rate, price_precision, self.precision_mode_price + ) if direction == "short": propose_rate = max(propose_rate, row[LOW_IDX]) else: @@ -1109,6 +1112,7 @@ class Backtesting: amount_precision=precision_amount, price_precision=precision_price, precision_mode=self.precision_mode, + precision_mode_price=self.precision_mode_price, contract_size=contract_size, orders=[], ) From cfa591838fae993009a7e8a12dc080156642aec3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 09:13:10 +0200 Subject: [PATCH 145/242] feat: use "precision_mode_price" where applicable --- freqtrade/exchange/exchange.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index dc9c1d0bb..53dff0066 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -918,7 +918,10 @@ class Exchange: For stoploss calculations, must use ROUND_UP for longs, and ROUND_DOWN for shorts. """ return price_to_precision( - price, self.get_precision_price(pair), self.precisionMode, rounding_mode=rounding_mode + price, + self.get_precision_price(pair), + self.precision_mode_price, + rounding_mode=rounding_mode, ) def price_get_one_pip(self, pair: str, price: float) -> float: From 350c2241c4a2fef1477fa722c99c79b45705c1c7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 09:20:40 +0200 Subject: [PATCH 146/242] test: adjust test mocks for precision_mode_price --- tests/conftest.py | 1 + tests/exchange/test_exchange.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index c0a9f2485..8f15388ef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -243,6 +243,7 @@ def patch_exchange( mocker.patch(f"{EXMS}.id", PropertyMock(return_value=exchange)) mocker.patch(f"{EXMS}.name", PropertyMock(return_value=exchange.title())) mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=2)) + mocker.patch(f"{EXMS}.precision_mode_price", PropertyMock(return_value=2)) # Temporary patch ... mocker.patch("freqtrade.exchange.bybit.Bybit.cache_leverage_tiers") diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 168157cea..4a1865658 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -365,6 +365,7 @@ def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precisio exchange = get_patched_exchange(mocker, default_conf, exchange="binance") mocker.patch(f"{EXMS}.markets", markets) mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=precision_mode)) + mocker.patch(f"{EXMS}.precision_mode_price", PropertyMock(return_value=precision_mode)) pair = "ETH/BTC" assert pytest.approx(exchange.price_get_one_pip(pair, price)) == expected From d7bee0c9e071cc73203109e11db95a15ca1957d7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 09:23:43 +0200 Subject: [PATCH 147/242] test: update further tests for precision_mode_price --- tests/rpc/test_rpc.py | 1 + tests/strategy/test_interface.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index a0c235cd5..e0eaabe24 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -96,6 +96,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: "amount_precision": 8.0, "price_precision": 8.0, "precision_mode": 2, + "precision_mode_price": 2, "contract_size": 1, "has_open_orders": False, "orders": [ diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index ab87e110e..5459a0ff2 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -579,6 +579,7 @@ def test_ft_stoploss_reached( liquidation_price=liq, price_precision=4, precision_mode=2, + precision_mode_price=2, ) trade.adjust_min_max_rates(trade.open_rate, trade.open_rate) strategy.trailing_stop = trailing From 0b8dfa6878ab4df1ebf6c7f77f92d5b6844e5f13 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 09:29:36 +0200 Subject: [PATCH 148/242] chore: improved docstring for precision_mode_price --- freqtrade/exchange/exchange.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 53dff0066..83db916bd 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -416,12 +416,17 @@ class Exchange: @property def precisionMode(self) -> int: - """exchange ccxt precisionMode""" + """Exchange ccxt precisionMode""" return self._api.precisionMode @property def precision_mode_price(self) -> int: - """exchange ccxt precisionMode""" + """ + Exchange ccxt precisionMode used for price + Workaround for ccxt limitation to not have precisionMode for price + if it differs for an exchange + Might need to be updated if https://github.com/ccxt/ccxt/issues/20408 is fixed. + """ return self._api.precisionMode def additional_exchange_init(self) -> None: From aa6c30ade6a79d36e9d8cbeea940d316aa014e40 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 09:50:34 +0200 Subject: [PATCH 149/242] chore: fix line too long issue --- freqtrade/persistence/trade_model.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 67f5b37f1..4e7f01906 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -1698,7 +1698,9 @@ class Trade(ModelBase, LocalTrade): ) price_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore precision_mode: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore - precision_mode_price: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore + precision_mode_price: Mapped[Optional[int]] = mapped_column( # type: ignore + Integer, nullable=True + ) contract_size: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore # Leverage trading properties From f64786543d76bdac5198c3af66df0aaaa532de2d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 09:49:46 +0200 Subject: [PATCH 150/242] feat: hyperliquid requires different precision modes --- freqtrade/exchange/hyperliquid.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index 9b8598432..1255b977a 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -3,6 +3,8 @@ import logging from typing import Dict +from ccxt import SIGNIFICANT_DIGITS + from freqtrade.exchange import Exchange @@ -22,3 +24,10 @@ class Hyperliquid(Exchange): "trades_has_history": False, # Trades endpoint doesn't seem available. "exchange_has_overrides": {"fetchTrades": False}, } + + @property + def precision_mode_price(self) -> int: + """ + Override the default precision mode for price. + """ + return SIGNIFICANT_DIGITS From 6ea450a4e11ba0aa7de7e1a4b0a32fb862f30320 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2024 14:30:01 +0200 Subject: [PATCH 151/242] chore: bitvavo uses DECIMAL_PLACES for amount rounding closes #9560 --- freqtrade/exchange/bitvavo.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/bitvavo.py b/freqtrade/exchange/bitvavo.py index d088e3435..ff0c0e37f 100644 --- a/freqtrade/exchange/bitvavo.py +++ b/freqtrade/exchange/bitvavo.py @@ -1,8 +1,10 @@ -"""Kucoin exchange subclass.""" +"""Bitvavo exchange subclass.""" import logging from typing import Dict +from ccxt import DECIMAL_PLACES + from freqtrade.exchange import Exchange @@ -22,3 +24,11 @@ class Bitvavo(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 1440, } + + @property + def precisionMode(self) -> int: + """ + Exchange ccxt precisionMode + Override due to https://github.com/ccxt/ccxt/issues/20408 + """ + return DECIMAL_PLACES From 23510c80bea33fdb7fa1dfbc9ddb4146d2ec4fab Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 14 Aug 2024 08:19:58 +0200 Subject: [PATCH 152/242] fix: don't auto-populate non-existing secret entries --- freqtrade/configuration/config_secrets.py | 6 ++++-- tests/test_configuration.py | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration/config_secrets.py b/freqtrade/configuration/config_secrets.py index e17a7925e..2bb0d97f8 100644 --- a/freqtrade/configuration/config_secrets.py +++ b/freqtrade/configuration/config_secrets.py @@ -37,8 +37,10 @@ def sanitize_config(config: Config, *, show_sensitive: bool = False) -> Config: nested_config = config for nested_key in nested_keys[:-1]: nested_config = nested_config.get(nested_key, {}) - nested_config[nested_keys[-1]] = "REDACTED" + if nested_keys[-1] in nested_config: + nested_config[nested_keys[-1]] = "REDACTED" else: - config[key] = "REDACTED" + if key in config: + config[key] = "REDACTED" return config diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 9f9081ab0..d77fae6a8 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1653,9 +1653,12 @@ def test_sanitize_config(default_conf_usdt): res = sanitize_config(default_conf_usdt) # Didn't modify original dict assert default_conf_usdt["exchange"]["key"] != "REDACTED" + assert "accountId" not in default_conf_usdt["exchange"] assert res["exchange"]["key"] == "REDACTED" assert res["exchange"]["secret"] == "REDACTED" + # Didn't add a non-existing key + assert "accountId" not in res["exchange"] res = sanitize_config(default_conf_usdt, show_sensitive=True) assert res["exchange"]["key"] == default_conf_usdt["exchange"]["key"] From 331159a3d832256738988ab4bfa2fed2d4c53112 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 14 Aug 2024 21:18:47 +0200 Subject: [PATCH 153/242] fix: ensure handle_onexchange_order works without false warnings futures were not properly handled in this command. closes #10533 --- freqtrade/freqtradebot.py | 6 +++++- freqtrade/wallets.py | 11 +++++++++++ tests/freqtradebot/test_freqtradebot.py | 2 +- tests/test_wallets.py | 4 ++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index c970e440b..dff99e93e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -542,7 +542,11 @@ class FreqtradeBot(LoggingMixin): ) else: trade.exit_reason = prev_exit_reason - total = self.wallets.get_total(trade.base_currency) if trade.base_currency else 0 + total = ( + self.wallets.get_owned(trade.pair, trade.base_currency) + if trade.base_currency + else 0 + ) if total < trade.amount: if trade.fully_canceled_entry_order_count == len(trade.orders): logger.warning( diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 1e7281ddd..336f24b77 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -66,6 +66,17 @@ class Wallets: else: return 0 + def get_owned(self, pair: str, base_currency: str) -> float: + """ + Get currently owned value. + Designed to work across both spot and futures. + """ + if self._config.get("trading_mode", "spot") != TradingMode.FUTURES: + return self.get_total(base_currency) or 0 + if pos := self._positions.get(pair): + return pos.position + return 0 + def _update_dry(self) -> None: """ Update from database in dry-run mode diff --git a/tests/freqtradebot/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py index 23dfbb785..7c45928a2 100644 --- a/tests/freqtradebot/test_freqtradebot.py +++ b/tests/freqtradebot/test_freqtradebot.py @@ -4909,7 +4909,7 @@ def test_handle_onexchange_order_changed_amount( leverage=1, ) freqtrade.wallets = MagicMock() - freqtrade.wallets.get_total = MagicMock(return_value=entry_order["amount"] * factor) + freqtrade.wallets.get_owned = MagicMock(return_value=entry_order["amount"] * factor) trade.orders.append(Order.parse_from_ccxt_object(entry_order, "ADA/USDT", entry_side(is_short))) Trade.session.add(trade) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index f33222b7c..d68fbd226 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -65,6 +65,7 @@ def test_sync_wallet_at_boot(mocker, default_conf): assert freqtrade.wallets.get_free("GAS") == 0.270739 assert freqtrade.wallets.get_used("GAS") == 0.1 assert freqtrade.wallets.get_total("GAS") == 0.260439 + assert freqtrade.wallets.get_owned("GAS/USDT", "GAS") == 0.260439 update_mock = mocker.patch("freqtrade.wallets.Wallets._update_live") freqtrade.wallets.update(False) assert update_mock.call_count == 0 @@ -74,6 +75,7 @@ def test_sync_wallet_at_boot(mocker, default_conf): assert freqtrade.wallets.get_free("NOCURRENCY") == 0 assert freqtrade.wallets.get_used("NOCURRENCY") == 0 assert freqtrade.wallets.get_total("NOCURRENCY") == 0 + assert freqtrade.wallets.get_owned("NOCURRENCY/USDT", "NOCURRENCY") == 0 def test_sync_wallet_missing_data(mocker, default_conf): @@ -336,6 +338,8 @@ def test_sync_wallet_futures_live(mocker, default_conf): assert "USDT" in freqtrade.wallets._wallets assert "ETH/USDT:USDT" in freqtrade.wallets._positions assert freqtrade.wallets._last_wallet_refresh is not None + assert freqtrade.wallets.get_owned("ETH/USDT:USDT", "ETH") == 1000 + assert freqtrade.wallets.get_owned("SOL/USDT:USDT", "SOL") == 0 # Remove ETH/USDT:USDT position del mock_result[0] From 77b32e94f14b05212dd8d683d0ce312c4e798870 Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Thu, 15 Aug 2024 03:12:42 +0000 Subject: [PATCH 154/242] chore: update pre-commit hooks --- .../exchange/binance_leverage_tiers.json | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 129e3a573..ca672a9a8 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -5851,6 +5851,136 @@ } } ], + "BANANA/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "5000", + "maintMarginRatio": "0.02", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "125.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "750.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10750.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "20750.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83250.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333250.0" + } + } + ], "BAND/USDT:USDT": [ { "tier": 1.0, From d9f6f0847da7d6caa98f83812d6883f7f83ac41e Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 06:50:04 +0200 Subject: [PATCH 155/242] docs: improve readability of hyperopt-loss sample --- docs/advanced-hyperopt.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/advanced-hyperopt.md b/docs/advanced-hyperopt.md index eb8bf3f84..e276bed94 100644 --- a/docs/advanced-hyperopt.md +++ b/docs/advanced-hyperopt.md @@ -30,11 +30,17 @@ class SuperDuperHyperOptLoss(IHyperOptLoss): """ @staticmethod - def hyperopt_loss_function(results: DataFrame, trade_count: int, - min_date: datetime, max_date: datetime, - config: Config, processed: Dict[str, DataFrame], - backtest_stats: Dict[str, Any], - *args, **kwargs) -> float: + def hyperopt_loss_function( + *, + results: DataFrame, + trade_count: int, + min_date: datetime, + max_date: datetime, + config: Config, + processed: Dict[str, DataFrame], + backtest_stats: Dict[str, Any], + **kwargs, + ) -> float: """ Objective function, returns smaller number for better results This is the legacy algorithm (used until now in freqtrade). From 21c5c919eaa1363c260c8dbde797e4fcfadc8691 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 06:50:22 +0200 Subject: [PATCH 156/242] chore: Improve typehinting for hyperopt --- freqtrade/optimize/hyperopt.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 2006cee58..574a669f6 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -168,7 +168,9 @@ class Hyperopt: cloudpickle.register_pickle_by_value(sys.modules[modules.__module__]) self.hyperopt_pickle_magic(modules.__bases__) - def _get_params_dict(self, dimensions: List[Dimension], raw_params: List[Any]) -> Dict: + def _get_params_dict( + self, dimensions: List[Dimension], raw_params: List[Any] + ) -> Dict[str, Any]: # Ensure the number of dimensions match # the number of parameters in the list. if len(raw_params) != len(dimensions): @@ -317,7 +319,7 @@ class Hyperopt: + self.max_open_trades_space ) - def assign_params(self, params_dict: Dict, category: str) -> None: + def assign_params(self, params_dict: Dict[str, Any], category: str) -> None: """ Assign hyperoptable parameters """ @@ -404,7 +406,12 @@ class Hyperopt: ) def _get_results_dict( - self, backtesting_results, min_date, max_date, params_dict, processed: Dict[str, DataFrame] + self, + backtesting_results: Dict[str, Any], + min_date: datetime, + max_date: datetime, + params_dict: Dict[str, Any], + processed: Dict[str, DataFrame], ) -> Dict[str, Any]: params_details = self._get_params_details(params_dict) From 1b0ba0fa688110039a23094a96dd8198bfbbb03a Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 06:58:43 +0200 Subject: [PATCH 157/242] fix: typo in armhf dockerfile causing build to fail --- docker/Dockerfile.armhf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile.armhf b/docker/Dockerfile.armhf index 8f4736877..ed3c5fbde 100644 --- a/docker/Dockerfile.armhf +++ b/docker/Dockerfile.armhf @@ -17,7 +17,7 @@ RUN mkdir /freqtrade \ && chown ftuser:ftuser /freqtrade \ # Allow sudoers && echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers \ - && pip install --upgrade dpip + && pip install --upgrade pip WORKDIR /freqtrade From 646ed50f37ac6f2153efb49c8371535d0878fc09 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 07:29:19 +0200 Subject: [PATCH 158/242] chore: improve typing for balance endpoint --- freqtrade/exchange/exchange.py | 4 ++-- freqtrade/exchange/kraken.py | 4 ++-- freqtrade/exchange/types.py | 8 ++++++++ freqtrade/wallets.py | 6 +++--- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 546e6baf4..a7fe0be64 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -88,7 +88,7 @@ from freqtrade.exchange.exchange_utils_timeframe import ( timeframe_to_seconds, ) from freqtrade.exchange.exchange_ws import ExchangeWS -from freqtrade.exchange.types import OHLCVResponse, OrderBook, Ticker, Tickers +from freqtrade.exchange.types import CcxtBalances, OHLCVResponse, OrderBook, Ticker, Tickers from freqtrade.misc import ( chunks, deep_merge_dicts, @@ -1663,7 +1663,7 @@ class Exchange: return order @retrier - def get_balances(self) -> dict: + def get_balances(self) -> CcxtBalances: try: balances = self._api.fetch_balance() # Remove additional info from ccxt results diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index f0562ecaf..4b178420a 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -12,7 +12,7 @@ from freqtrade.enums import MarginMode, TradingMode from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exchange import Exchange from freqtrade.exchange.common import retrier -from freqtrade.exchange.types import Tickers +from freqtrade.exchange.types import CcxtBalances, Tickers logger = logging.getLogger(__name__) @@ -57,7 +57,7 @@ class Kraken(Exchange): return super().get_tickers(symbols=symbols, cached=cached) @retrier - def get_balances(self) -> dict: + def get_balances(self) -> CcxtBalances: if self._config["dry_run"]: return {} diff --git a/freqtrade/exchange/types.py b/freqtrade/exchange/types.py index a0d315c78..564505289 100644 --- a/freqtrade/exchange/types.py +++ b/freqtrade/exchange/types.py @@ -25,6 +25,14 @@ class OrderBook(TypedDict): nonce: Optional[int] +class CcxtBalance(TypedDict): + free: float + used: float + total: float + + +CcxtBalances = Dict[str, CcxtBalance] + Tickers = Dict[str, Ticker] # pair, timeframe, candleType, OHLCV, drop last?, diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 336f24b77..f0e36b61c 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -139,9 +139,9 @@ class Wallets: if isinstance(balances[currency], dict): self._wallets[currency] = Wallet( currency, - balances[currency].get("free"), - balances[currency].get("used"), - balances[currency].get("total"), + balances[currency].get("free", 0), + balances[currency].get("used", 0), + balances[currency].get("total", 0), ) # Remove currencies no longer in get_balances output for currency in deepcopy(self._wallets): From 04cdd807bab15701f565516ac9d955ec79e808ac Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 07:30:21 +0200 Subject: [PATCH 159/242] chore: improved type ordering --- freqtrade/exchange/types.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/types.py b/freqtrade/exchange/types.py index 564505289..357b3a40f 100644 --- a/freqtrade/exchange/types.py +++ b/freqtrade/exchange/types.py @@ -16,6 +16,9 @@ class Ticker(TypedDict): # Several more - only listing required. +Tickers = Dict[str, Ticker] + + class OrderBook(TypedDict): symbol: str bids: List[Tuple[float, float]] @@ -33,7 +36,6 @@ class CcxtBalance(TypedDict): CcxtBalances = Dict[str, CcxtBalance] -Tickers = Dict[str, Ticker] # pair, timeframe, candleType, OHLCV, drop last?, OHLCVResponse = Tuple[str, str, CandleType, List, bool] From 5ad23405b76b4971c422290ceee338c27df83470 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 08:06:50 +0200 Subject: [PATCH 160/242] chore: align safevalue_fallback types --- freqtrade/misc.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 23e2779a0..7c56231c3 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -128,7 +128,10 @@ def round_dict(d, n): return {k: (round(v, n) if isinstance(v, float) else v) for k, v in d.items()} -def safe_value_fallback(obj: dict, key1: str, key2: Optional[str] = None, default_value=None): +DictMap = Union[Dict[str, Any], Mapping[str, Any]] + + +def safe_value_fallback(obj: DictMap, key1: str, key2: Optional[str] = None, default_value=None): """ Search a value in obj, return this if it's not None. Then search key2 in obj - return that if it's not none - then use default_value. @@ -142,10 +145,7 @@ def safe_value_fallback(obj: dict, key1: str, key2: Optional[str] = None, defaul return default_value -dictMap = Union[Dict[str, Any], Mapping[str, Any]] - - -def safe_value_fallback2(dict1: dictMap, dict2: dictMap, key1: str, key2: str, default_value=None): +def safe_value_fallback2(dict1: DictMap, dict2: DictMap, key1: str, key2: str, default_value=None): """ Search a value in dict1, return this if it's not None. Fall back to dict2 - return key2 from dict2 if it's not None. From d52169930563e2ec9bb3eefa092a4c1d70360d15 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 08:07:45 +0200 Subject: [PATCH 161/242] refactor: type fetch_positions response --- freqtrade/exchange/exchange.py | 13 ++++++++++--- freqtrade/exchange/types.py | 10 ++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index a7fe0be64..9e58acc27 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -88,7 +88,14 @@ from freqtrade.exchange.exchange_utils_timeframe import ( timeframe_to_seconds, ) from freqtrade.exchange.exchange_ws import ExchangeWS -from freqtrade.exchange.types import CcxtBalances, OHLCVResponse, OrderBook, Ticker, Tickers +from freqtrade.exchange.types import ( + CcxtBalances, + CcxtPosition, + OHLCVResponse, + OrderBook, + Ticker, + Tickers, +) from freqtrade.misc import ( chunks, deep_merge_dicts, @@ -1683,7 +1690,7 @@ class Exchange: raise OperationalException(e) from e @retrier - def fetch_positions(self, pair: Optional[str] = None) -> List[Dict]: + def fetch_positions(self, pair: Optional[str] = None) -> List[CcxtPosition]: """ Fetch positions from the exchange. If no pair is given, all positions are returned. @@ -1695,7 +1702,7 @@ class Exchange: symbols = [] if pair: symbols.append(pair) - positions: List[Dict] = self._api.fetch_positions(symbols) + positions: List[CcxtPosition] = self._api.fetch_positions(symbols) self._log_exchange_response("fetch_positions", positions) return positions except ccxt.DDoSProtection as e: diff --git a/freqtrade/exchange/types.py b/freqtrade/exchange/types.py index 357b3a40f..2a9ae1078 100644 --- a/freqtrade/exchange/types.py +++ b/freqtrade/exchange/types.py @@ -37,5 +37,15 @@ class CcxtBalance(TypedDict): CcxtBalances = Dict[str, CcxtBalance] +class CcxtPosition(TypedDict): + symbol: str + side: str + contracts: float + leverage: float + collateral: Optional[float] + initialMargin: Optional[float] + liquidationPrice: Optional[float] + + # pair, timeframe, candleType, OHLCV, drop last?, OHLCVResponse = Tuple[str, str, CandleType, List, bool] From 2ffe938206cb44600986c87b9faa9134b2655699 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 08:21:25 +0200 Subject: [PATCH 162/242] test: update test behavior - wallets has 0, never none --- tests/test_wallets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index d68fbd226..a2aebeea4 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -97,7 +97,7 @@ def test_sync_wallet_missing_data(mocker, default_conf): assert freqtrade.wallets._wallets["BNT"].used == 2.0 assert freqtrade.wallets._wallets["BNT"].total == 3.0 assert freqtrade.wallets._wallets["GAS"].free == 0.260739 - assert freqtrade.wallets._wallets["GAS"].used is None + assert freqtrade.wallets._wallets["GAS"].used == 0.0 assert freqtrade.wallets._wallets["GAS"].total == 0.260739 assert freqtrade.wallets.get_free("GAS") == 0.260739 From 756fef53f940f15bf36475c510c096fcb3f161c8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 15:44:59 +0200 Subject: [PATCH 163/242] refactor: improve live positions update --- freqtrade/wallets.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index f0e36b61c..2840d9335 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -149,7 +149,7 @@ class Wallets: del self._wallets[currency] positions = self._exchange.fetch_positions() - self._positions = {} + _parsed_positions = {} for position in positions: symbol = position["symbol"] if position["side"] is None or position["collateral"] == 0.0: @@ -158,13 +158,14 @@ class Wallets: size = self._exchange._contracts_to_amount(symbol, position["contracts"]) collateral = safe_value_fallback(position, "collateral", "initialMargin", 0.0) leverage = position["leverage"] - self._positions[symbol] = PositionWallet( + _parsed_positions[symbol] = PositionWallet( symbol, position=size, leverage=leverage, collateral=collateral, side=position["side"], ) + self._positions = _parsed_positions def update(self, require_update: bool = True) -> None: """ From 34667c69d33495904b13ba07081a1d29f7367752 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 16:59:30 +0200 Subject: [PATCH 164/242] chore: remove leverage from /balance endpoint --- freqtrade/rpc/api_server/api_schemas.py | 1 - freqtrade/rpc/rpc.py | 2 -- freqtrade/rpc/telegram.py | 1 - freqtrade/wallets.py | 4 ++-- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 0e36c0992..6ba65d0ec 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -67,7 +67,6 @@ class Balance(BaseModel): stake: str # Starting with 2.x side: str - leverage: float is_position: bool position: float is_bot_managed: bool diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 3feb4860c..aee8bd725 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -742,7 +742,6 @@ class RPC: "est_stake_bot": est_stake_bot if is_bot_managed else 0, "stake": stake_currency, "side": "long", - "leverage": 1, "position": 0, "is_bot_managed": is_bot_managed, "is_position": False, @@ -764,7 +763,6 @@ class RPC: "est_stake": position.collateral, "est_stake_bot": position.collateral, "stake": stake_currency, - "leverage": position.leverage, "side": position.side, "is_bot_managed": True, "is_position": True, diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 8eaa970c8..22b574621 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1133,7 +1133,6 @@ class Telegram(RPCHandler): curr_output = ( f"*{curr['currency']}:*\n" f"\t`{curr['side']}: {curr['position']:.8f}`\n" - f"\t`Leverage: {curr['leverage']:.1f}`\n" f"\t`Est. {curr['stake']}: " f"{fmt_coin(curr['est_stake'], curr['stake'], False)}`\n" ) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 2840d9335..f888ef92e 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -29,7 +29,7 @@ class Wallet(NamedTuple): class PositionWallet(NamedTuple): symbol: str position: float = 0 - leverage: float = 0 + leverage: Optional[float] = 0 # Don't use this - it's not guaranteed to be set collateral: float = 0 side: str = "long" @@ -157,7 +157,7 @@ class Wallets: continue size = self._exchange._contracts_to_amount(symbol, position["contracts"]) collateral = safe_value_fallback(position, "collateral", "initialMargin", 0.0) - leverage = position["leverage"] + leverage = position.get("leverage") _parsed_positions[symbol] = PositionWallet( symbol, position=size, From 36098f6b785811d0f174bb3812dd394120b2e32b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 17:36:06 +0200 Subject: [PATCH 165/242] test: update tests for removal of leverage --- tests/rpc/test_rpc.py | 4 ---- tests/rpc/test_rpc_apiserver.py | 1 - 2 files changed, 5 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index e0eaabe24..feb7f9f1a 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -601,7 +601,6 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): "est_stake_bot": 0, "stake": "USDT", "side": "long", - "leverage": 1, "position": 0, "is_bot_managed": False, "is_position": False, @@ -616,7 +615,6 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): "est_stake_bot": 0, "stake": "USDT", "side": "long", - "leverage": 1, "position": 0, "is_bot_managed": False, "is_position": False, @@ -631,7 +629,6 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): "est_stake_bot": 49.5, "stake": "USDT", "side": "long", - "leverage": 1, "position": 0, "is_bot_managed": True, "is_position": False, @@ -645,7 +642,6 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): "est_stake": 20, "est_stake_bot": 20, "stake": "USDT", - "leverage": 5.0, "side": "short", "is_bot_managed": True, "is_position": True, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 98513f290..02b8729a6 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -574,7 +574,6 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers): "est_stake_bot": pytest.approx(11.879999), "stake": "BTC", "is_position": False, - "leverage": 1.0, "position": 0.0, "side": "long", "is_bot_managed": True, From 8498cb17e7db98e75b7163d714841d162cf2dd4f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 17:09:54 +0200 Subject: [PATCH 166/242] test: add explicit test for telegram's short behavior --- tests/rpc/test_rpc_telegram.py | 50 ++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 6e4aa3384..6cc48d6ef 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -990,6 +990,56 @@ async def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance assert "*Estimated Value (Bot managed assets only)*:" in result +async def test_telegram_balance_handle_futures( + default_conf, update, rpc_balance, mocker, tickers +) -> None: + default_conf.update( + { + "dry_run": False, + "trading_mode": "futures", + "margin_mode": "isolated", + } + ) + mock_pos = [ + { + "symbol": "ETH/USDT:USDT", + "timestamp": None, + "datetime": None, + "initialMargin": 0.0, + "initialMarginPercentage": None, + "maintenanceMargin": 0.0, + "maintenanceMarginPercentage": 0.005, + "entryPrice": 0.0, + "notional": 10.0, + "leverage": 5.0, + "unrealizedPnl": 0.0, + "contracts": 1.0, + "contractSize": 1, + "marginRatio": None, + "liquidationPrice": 0.0, + "markPrice": 2896.41, + "collateral": 20, + "marginType": "isolated", + "side": "short", + "percentage": None, + } + ] + mocker.patch(f"{EXMS}.get_balances", return_value=rpc_balance) + mocker.patch(f"{EXMS}.fetch_positions", return_value=mock_pos) + mocker.patch(f"{EXMS}.get_tickers", tickers) + mocker.patch(f"{EXMS}.get_valid_pair_combination", side_effect=lambda a, b: f"{a}/{b}") + + telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) + patch_get_signal(freqtradebot) + + await telegram._balance(update=update, context=MagicMock()) + result = msg_mock.call_args_list[0][0][0] + assert msg_mock.call_count == 1 + + assert "ETH/USDT:USDT" in result + assert "`short: 10" in result + + async def test_balance_handle_empty_response(default_conf, update, mocker) -> None: default_conf["dry_run"] = False mocker.patch(f"{EXMS}.get_balances", return_value={}) From 3a676f98dba6a58ffe6b4a072c7bc64d5837c1dd Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 17:11:06 +0200 Subject: [PATCH 167/242] test: improve telegram balance test --- tests/rpc/test_rpc_telegram.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 6cc48d6ef..971e13846 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1022,7 +1022,29 @@ async def test_telegram_balance_handle_futures( "marginType": "isolated", "side": "short", "percentage": None, - } + }, + { + "symbol": "XRP/USDT:USDT", + "timestamp": None, + "datetime": None, + "initialMargin": 0.0, + "initialMarginPercentage": None, + "maintenanceMargin": 0.0, + "maintenanceMarginPercentage": 0.005, + "entryPrice": 0.0, + "notional": 10.0, + "leverage": None, + "unrealizedPnl": 0.0, + "contracts": 1.0, + "contractSize": 1, + "marginRatio": None, + "liquidationPrice": 0.0, + "markPrice": 2896.41, + "collateral": 20, + "marginType": "isolated", + "side": "short", + "percentage": None, + }, ] mocker.patch(f"{EXMS}.get_balances", return_value=rpc_balance) mocker.patch(f"{EXMS}.fetch_positions", return_value=mock_pos) @@ -1038,6 +1060,7 @@ async def test_telegram_balance_handle_futures( assert "ETH/USDT:USDT" in result assert "`short: 10" in result + assert "XRP/USDT:USDT" in result async def test_balance_handle_empty_response(default_conf, update, mocker) -> None: From fdad24aaac13af6ea606c7923a67a43ac97c4fad Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 17:56:54 +0200 Subject: [PATCH 168/242] feat: add leverage to telegram's /status table --- freqtrade/rpc/rpc.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index aee8bd725..0c555e860 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -296,7 +296,10 @@ class RPC: else: trade_profit = 0.0 profit_str = f"{0.0:.2f}" - direction_str = ("S" if trade.is_short else "L") if nonspot else "" + leverage = f"{trade.leverage:.3g}" + direction_str = ( + (f"S {leverage}x" if trade.is_short else f"L {leverage}x") if nonspot else "" + ) if self._fiat_converter: fiat_profit = self._fiat_converter.convert_amount( trade_profit, stake_currency, fiat_display_currency From f341edb97574d92ab9c01cd9ee9340df95d44024 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 19:54:08 +0200 Subject: [PATCH 169/242] feat: Enable websocket support for okx --- freqtrade/exchange/okx.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index 94a81b452..db94f576d 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -34,6 +34,7 @@ class Okx(Exchange): "stoploss_order_types": {"limit": "limit"}, "stoploss_on_exchange": True, "trades_has_history": False, # Endpoint doesn't have a "since" parameter + "ws.enabled": True, } _ft_has_futures: Dict = { "tickers_have_quoteVolume": False, @@ -43,6 +44,7 @@ class Okx(Exchange): PriceType.MARK: "index", PriceType.INDEX: "mark", }, + "ws.enabled": True, } _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ From e26ac6ed006e44df432f9d6483161a808ac62876 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Aug 2024 20:02:45 +0200 Subject: [PATCH 170/242] test: speed up detail test --- tests/optimize/test_backtesting.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 5bc113a62..d159c8602 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1607,7 +1607,7 @@ def test_backtest_multi_pair_detail( mocker.patch(f"{EXMS}.get_fee", fee) patch_exchange(mocker) - raw_candles_1m = generate_test_data("1m", 2500, "2022-01-03 12:00:00+00:00") + raw_candles_1m = generate_test_data("1m", 1000, "2022-01-03 12:00:00+00:00") raw_candles = ohlcv_fill_up_missing_data(raw_candles_1m, "5m", "dummy") pairs = ["ADA/USDT", "DASH/USDT", "ETH/USDT", "LTC/USDT", "NXT/USDT"] @@ -1615,7 +1615,7 @@ def test_backtest_multi_pair_detail( detail_data = {pair: raw_candles_1m for pair in pairs} # Only use 500 lines to increase performance - data = trim_dictlist(data, -500) + data = trim_dictlist(data, -200) # Remove data for one pair from the beginning of the data if tres > 0: @@ -1644,17 +1644,17 @@ def test_backtest_multi_pair_detail( results = backtesting.backtest(**backtest_conf) # bot_loop_start is called once per candle. - assert backtesting.strategy.bot_loop_start.call_count == 499 + assert backtesting.strategy.bot_loop_start.call_count == 199 # Validated row once per candle and pair - assert vr_spy.call_count == 2495 + assert vr_spy.call_count == 995 if use_detail: # Backtest loop is called once per candle per pair # Exact numbers depend on trade state - but should be around 3_800 - assert bl_spy.call_count > 3_800 - assert bl_spy.call_count < 3_900 + assert bl_spy.call_count > 1_350 + assert bl_spy.call_count < 1_500 else: - assert bl_spy.call_count < 2495 + assert bl_spy.call_count < 995 # Make sure we have parallel trades assert len(evaluate_result_multi(results["results"], "5m", 2)) > 0 From 4ca6e617268442e945593dc57c9b60f5116f6dbf Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 16 Aug 2024 18:27:30 +0200 Subject: [PATCH 171/242] fix: use dynamic trading_mode for trades loading closes #10540 --- freqtrade/data/dataprovider.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index 809dc5c02..6db9831b3 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -521,15 +521,12 @@ class DataProvider: (pair, timeframe or self._config["timeframe"], _candle_type), copy=copy ) elif self.runmode in (RunMode.BACKTEST, RunMode.HYPEROPT): - _candle_type = ( - CandleType.from_string(candle_type) - if candle_type != "" - else self._config["candle_type_def"] - ) data_handler = get_datahandler( self._config["datadir"], data_format=self._config["dataformat_trades"] ) - trades_df = data_handler.trades_load(pair, TradingMode.FUTURES) + trades_df = data_handler.trades_load( + pair, self._config.get("trading_mode", TradingMode.SPOT) + ) return trades_df else: From f0a25ea4858f66db5c440edefdc04ba815c28a4b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 9 Apr 2024 20:32:15 +0200 Subject: [PATCH 172/242] feat: Add __all__ export to strategy's init file --- freqtrade/strategy/__init__.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py index bb21100c4..0a492a29e 100644 --- a/freqtrade/strategy/__init__.py +++ b/freqtrade/strategy/__init__.py @@ -1,4 +1,6 @@ # flake8: noqa: F401 +from typing import Dict, List, Optional, Union + from freqtrade.exchange import ( timeframe_to_minutes, timeframe_to_msecs, @@ -6,6 +8,7 @@ from freqtrade.exchange import ( timeframe_to_prev_date, timeframe_to_seconds, ) +from freqtrade.persistence import Order, PairLocks, Trade from freqtrade.strategy.informative_decorator import informative from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.parameters import ( @@ -20,3 +23,30 @@ from freqtrade.strategy.strategy_helper import ( stoploss_from_absolute, stoploss_from_open, ) + + +__all__ = [ + "timeframe_to_minutes", + "timeframe_to_next_date", + "timeframe_to_prev_date", + "informative", + "IStrategy", + "Trade", + "Order", + "PairLocks", + # Parameters + "BooleanParameter", + "CategoricalParameter", + "DecimalParameter", + "IntParameter", + "RealParameter", + # Strategy helper functions + "merge_informative_pair", + "stoploss_from_absolute", + "stoploss_from_open", + # Typings + "List", + "Optional", + "Union", + "Dict", +] From 27a4a502d7893924051c0ce7a3cea9275a38aa64 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:24:50 +0200 Subject: [PATCH 173/242] docs: Add section explaining strategy imports --- docs/includes/strategy-imports.md | 41 +++++++++++++++++++++++++++++++ docs/strategy-callbacks.md | 2 ++ docs/strategy-customization.md | 2 ++ 3 files changed, 45 insertions(+) create mode 100644 docs/includes/strategy-imports.md diff --git a/docs/includes/strategy-imports.md b/docs/includes/strategy-imports.md new file mode 100644 index 000000000..818af3a52 --- /dev/null +++ b/docs/includes/strategy-imports.md @@ -0,0 +1,41 @@ +## Imports necessary for a strategy + +When creating a strategy, you will need to import the necessary modules and classes. The following imports are required for a strategy: + +By default, we recommend the following imports as a base line for your strategy: +This will cover all imports necessary for freqtrade functions to work. +Obviously you can add more imports as needed for your strategy. + +``` python +# flake8: noqa: F401 +# isort: skip_file +# --- Do not remove these imports --- +import numpy as np +import pandas as pd +from datetime import datetime +from pandas import DataFrame +from typing import Optional, Union + +from freqtrade.strategy import ( + IStrategy, + Trade, + Order, + PairLocks, + informative, # @informative decorator + # Hyperopt Parameters + BooleanParameter, + CategoricalParameter, + DecimalParameter, + IntParameter, + RealParameter, + # Strategy helper functions + merge_informative_pair, + stoploss_from_absolute, + stoploss_from_open, +) + +# -------------------------------- +# Add your lib to import here +import talib.abstract as ta +import freqtrade.vendor.qtpylib.indicators as qtpylib +``` diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 74eef53c1..a090749cc 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -24,6 +24,8 @@ Currently available callbacks: !!! Tip "Callback calling sequence" You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic) +--8<-- "includes/strategy-imports.md" + ## Bot start A simple callback which is called once when the strategy is loaded. diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 98d7ae9d2..a8b9dcb4c 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -407,6 +407,8 @@ Currently this is `pair`, which can be accessed using `metadata['pair']` - and w The Metadata-dict should not be modified and does not persist information across multiple calls. Instead, have a look at the [Storing information](strategy-advanced.md#storing-information-persistent) section. +--8<-- "includes/strategy-imports.md" + ## Strategy file loading By default, freqtrade will attempt to load strategies from all `.py` files within `user_data/strategies`. From 6c131b56486f84b8e006ed9d6b72d02375940935 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:25:47 +0200 Subject: [PATCH 174/242] chore: add comment to better explain imports --- freqtrade/strategy/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py index 0a492a29e..6e8fb5da8 100644 --- a/freqtrade/strategy/__init__.py +++ b/freqtrade/strategy/__init__.py @@ -25,6 +25,7 @@ from freqtrade.strategy.strategy_helper import ( ) +# Imports to be used for `from freqtrade.strategy import *` __all__ = [ "timeframe_to_minutes", "timeframe_to_next_date", From d6f96b2c53870e04c525193b5195e3a7a1ae29fa Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:26:21 +0200 Subject: [PATCH 175/242] chore: remove typing imports These shouldn't be star imported, but should be explicitly imported. --- freqtrade/strategy/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py index 6e8fb5da8..d5fb9b7ae 100644 --- a/freqtrade/strategy/__init__.py +++ b/freqtrade/strategy/__init__.py @@ -1,5 +1,4 @@ # flake8: noqa: F401 -from typing import Dict, List, Optional, Union from freqtrade.exchange import ( timeframe_to_minutes, @@ -45,9 +44,4 @@ __all__ = [ "merge_informative_pair", "stoploss_from_absolute", "stoploss_from_open", - # Typings - "List", - "Optional", - "Union", - "Dict", ] From 5bc8b02b0febf2be0c97a57536f4c93fbf7167b1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:28:19 +0200 Subject: [PATCH 176/242] feat: Update imports for sample strategy --- docs/includes/strategy-imports.md | 2 +- freqtrade/templates/sample_strategy.py | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/includes/strategy-imports.md b/docs/includes/strategy-imports.md index 818af3a52..b3d75a5e3 100644 --- a/docs/includes/strategy-imports.md +++ b/docs/includes/strategy-imports.md @@ -14,7 +14,7 @@ import numpy as np import pandas as pd from datetime import datetime from pandas import DataFrame -from typing import Optional, Union +from typing import Dict, Optional, Union from freqtrade.strategy import ( IStrategy, diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 033c0d24e..950e1f225 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -2,17 +2,28 @@ # flake8: noqa: F401 # isort: skip_file # --- Do not remove these libs --- -import numpy as np # noqa -import pandas as pd # noqa +import numpy as np +import pandas as pd +from datetime import datetime from pandas import DataFrame -from typing import Optional, Union +from typing import Dict, Optional, Union from freqtrade.strategy import ( + IStrategy, + Trade, + Order, + PairLocks, + informative, # @informative decorator + # Hyperopt Parameters BooleanParameter, CategoricalParameter, DecimalParameter, - IStrategy, IntParameter, + RealParameter, + # Strategy helper functions + merge_informative_pair, + stoploss_from_absolute, + stoploss_from_open, ) # -------------------------------- From e7b57d8dee4230ae03ec89b8fce34cffd48eba85 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:28:56 +0200 Subject: [PATCH 177/242] chore: Update import for qtpylib to technical --- docs/includes/strategy-imports.md | 2 +- freqtrade/templates/sample_strategy.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/includes/strategy-imports.md b/docs/includes/strategy-imports.md index b3d75a5e3..14dde6c88 100644 --- a/docs/includes/strategy-imports.md +++ b/docs/includes/strategy-imports.md @@ -37,5 +37,5 @@ from freqtrade.strategy import ( # -------------------------------- # Add your lib to import here import talib.abstract as ta -import freqtrade.vendor.qtpylib.indicators as qtpylib +from technical import qtpylib ``` diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 950e1f225..3a9235bc2 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -29,7 +29,7 @@ from freqtrade.strategy import ( # -------------------------------- # Add your lib to import here import talib.abstract as ta -import freqtrade.vendor.qtpylib.indicators as qtpylib +from technical import qtpylib # This class is a sample. Feel free to customize it. From c2ac70ff10c3e6bf783f39e0571225bb058cb876 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:30:06 +0200 Subject: [PATCH 178/242] feat: update base_strategy to include all imports --- freqtrade/templates/base_strategy.py.j2 | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index a4e0a2b24..5c0e5e177 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -4,12 +4,27 @@ # --- Do not remove these libs --- import numpy as np import pandas as pd -from pandas import DataFrame from datetime import datetime -from typing import Optional, Union +from pandas import DataFrame +from typing import Dict, Optional, Union -from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, - IntParameter, IStrategy, merge_informative_pair) +from freqtrade.strategy import ( + IStrategy, + Trade, + Order, + PairLocks, + informative, # @informative decorator + # Hyperopt Parameters + BooleanParameter, + CategoricalParameter, + DecimalParameter, + IntParameter, + RealParameter, + # Strategy helper functions + merge_informative_pair, + stoploss_from_absolute, + stoploss_from_open, +) # -------------------------------- # Add your lib to import here From b3a042a63b70b4ba1a32ab10e27ecc5d6191a1d9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:32:38 +0200 Subject: [PATCH 179/242] feat: don't use commented typehints Imports are correct now --- .../strategy_methods_advanced.j2 | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 index 1783e818c..07b72610a 100644 --- a/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 @@ -13,9 +13,9 @@ def bot_loop_start(self, current_time: datetime, **kwargs) -> None: """ pass -def custom_entry_price(self, pair: str, trade: Optional['Trade'], - current_time: 'datetime', proposed_rate: float, - entry_tag: 'Optional[str]', side: str, **kwargs) -> float: +def custom_entry_price(self, pair: str, trade: Optional[Trade], + current_time: datetime, proposed_rate: float, + entry_tag: Optional[str], side: str, **kwargs) -> float: """ Custom entry price logic, returning the new entry price. @@ -33,7 +33,7 @@ def custom_entry_price(self, pair: str, trade: Optional['Trade'], """ return proposed_rate -def adjust_entry_price(self, trade: 'Trade', order: 'Optional[Order]', pair: str, +def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str, current_time: datetime, proposed_rate: float, current_order_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: """ @@ -61,8 +61,8 @@ def adjust_entry_price(self, trade: 'Trade', order: 'Optional[Order]', pair: str """ return current_order_rate -def custom_exit_price(self, pair: str, trade: 'Trade', - current_time: 'datetime', proposed_rate: float, +def custom_exit_price(self, pair: str, trade: Trade, + current_time: datetime, proposed_rate: float, current_profit: float, exit_tag: Optional[str], **kwargs) -> float: """ Custom exit price logic, returning the new exit price. @@ -104,7 +104,7 @@ def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: f use_custom_stoploss = True -def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, +def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> float: """ Custom stoploss logic, returning the new distance relative to current_rate (as ratio). @@ -126,8 +126,8 @@ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', c :return float: New stoploss value, relative to the current_rate """ -def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, - current_profit: float, **kwargs) -> 'Optional[Union[str, bool]]': +def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, + current_profit: float, **kwargs) -> Optional[Union[str, bool]]: """ Custom exit signal logic indicating that specified position should be sold. Returning a string or True from this method is equal to setting sell signal on a candle at specified @@ -177,9 +177,9 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f """ return True -def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float, +def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, time_in_force: str, exit_reason: str, - current_time: 'datetime', **kwargs) -> bool: + current_time: datetime, **kwargs) -> bool: """ Called right before placing a regular exit order. Timing for this function is critical, so avoid doing heavy computations or @@ -206,7 +206,7 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: """ return True -def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', +def check_entry_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ Check entry timeout function callback. @@ -228,7 +228,7 @@ def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', """ return False -def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order', +def check_exit_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ Check exit timeout function callback. @@ -250,7 +250,7 @@ def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order', """ return False -def adjust_trade_position(self, trade: 'Trade', current_time: datetime, +def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, min_stake: Optional[float], max_stake: float, current_entry_rate: float, current_exit_rate: float, @@ -302,7 +302,7 @@ def leverage(self, pair: str, current_time: datetime, current_rate: float, return 1.0 -def order_filled(self, pair: str, trade: 'Trade', order: 'Order', +def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None: """ Called right after an order fills. From 0995164110d110eafc758056007342d18d83b663 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:36:21 +0200 Subject: [PATCH 180/242] feat: improve formatting of generated strategy --- freqtrade/templates/base_strategy.py.j2 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index 5c0e5e177..2e8250dd2 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -93,8 +93,8 @@ class {{ strategy }}(IStrategy): buy_rsi = IntParameter(10, 40, default=30, space="buy") sell_rsi = IntParameter(60, 90, default=70, space="sell") - {{ attributes | indent(4) }} - {{ plot_config | indent(4) }} + {{- attributes | indent(4) }} + {{- plot_config | indent(4) }} def informative_pairs(self): """ @@ -120,7 +120,7 @@ class {{ strategy }}(IStrategy): :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ - {{ indicators | indent(8) }} + {{- indicators | indent(8) }} return dataframe @@ -172,4 +172,4 @@ class {{ strategy }}(IStrategy): 'exit_short'] = 1 """ return dataframe - {{ additional_methods | indent(4) }} + {{- additional_methods | indent(4) }} From 9408e858cd4c5ba54fcffb35907695a0224f87c7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:43:46 +0200 Subject: [PATCH 181/242] chore: use aligned quoting strategy for templtae --- freqtrade/templates/base_strategy.py.j2 | 10 +- .../strategy_subtemplates/buy_trend_full.j2 | 6 +- .../buy_trend_minimal.j2 | 2 +- .../strategy_subtemplates/indicators_full.j2 | 142 +++++++++--------- .../indicators_minimal.j2 | 10 +- .../strategy_subtemplates/plot_config_full.j2 | 14 +- .../strategy_subtemplates/sell_trend_full.j2 | 6 +- .../sell_trend_minimal.j2 | 2 +- .../strategy_attributes_full.j2 | 12 +- 9 files changed, 102 insertions(+), 102 deletions(-) diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index 2e8250dd2..a61093ebd 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -55,7 +55,7 @@ class {{ strategy }}(IStrategy): INTERFACE_VERSION = 3 # Optimal timeframe for the strategy. - timeframe = '5m' + timeframe = "5m" # Can this strategy go short? can_short: bool = False @@ -134,9 +134,9 @@ class {{ strategy }}(IStrategy): dataframe.loc[ ( {{ buy_trend | indent(16) }} - (dataframe['volume'] > 0) # Make sure Volume is not 0 + (dataframe["volume"] > 0) # Make sure Volume is not 0 ), - 'enter_long'] = 1 + "enter_long"] = 1 # Uncomment to use shorts (Only used in futures/margin mode. Check the documentation for more info) """ dataframe.loc[ @@ -159,9 +159,9 @@ class {{ strategy }}(IStrategy): dataframe.loc[ ( {{ sell_trend | indent(16) }} - (dataframe['volume'] > 0) # Make sure Volume is not 0 + (dataframe["volume"] > 0) # Make sure Volume is not 0 ), - 'exit_long'] = 1 + "exit_long"] = 1 # Uncomment to use shorts (Only used in futures/margin mode. Check the documentation for more info) """ dataframe.loc[ diff --git a/freqtrade/templates/strategy_subtemplates/buy_trend_full.j2 b/freqtrade/templates/strategy_subtemplates/buy_trend_full.j2 index aac8325a7..7a50fd4dc 100644 --- a/freqtrade/templates/strategy_subtemplates/buy_trend_full.j2 +++ b/freqtrade/templates/strategy_subtemplates/buy_trend_full.j2 @@ -1,3 +1,3 @@ -(qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi -(dataframe['tema'] <= dataframe['bb_middleband']) & # Guard: tema below BB middle -(dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising +(qtpylib.crossed_above(dataframe["rsi"], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi +(dataframe["tema"] <= dataframe["bb_middleband"]) & # Guard: tema below BB middle +(dataframe["tema"] > dataframe["tema"].shift(1)) & # Guard: tema is raising diff --git a/freqtrade/templates/strategy_subtemplates/buy_trend_minimal.j2 b/freqtrade/templates/strategy_subtemplates/buy_trend_minimal.j2 index e89d3779e..bcecacc3c 100644 --- a/freqtrade/templates/strategy_subtemplates/buy_trend_minimal.j2 +++ b/freqtrade/templates/strategy_subtemplates/buy_trend_minimal.j2 @@ -1 +1 @@ -(qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi +(qtpylib.crossed_above(dataframe["rsi"], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi diff --git a/freqtrade/templates/strategy_subtemplates/indicators_full.j2 b/freqtrade/templates/strategy_subtemplates/indicators_full.j2 index a497b47cb..e4c4daac4 100644 --- a/freqtrade/templates/strategy_subtemplates/indicators_full.j2 +++ b/freqtrade/templates/strategy_subtemplates/indicators_full.j2 @@ -3,24 +3,24 @@ # ------------------------------------ # ADX -dataframe['adx'] = ta.ADX(dataframe) +dataframe["adx"] = ta.ADX(dataframe) # # Plus Directional Indicator / Movement -# dataframe['plus_dm'] = ta.PLUS_DM(dataframe) -# dataframe['plus_di'] = ta.PLUS_DI(dataframe) +# dataframe["plus_dm"] = ta.PLUS_DM(dataframe) +# dataframe["plus_di"] = ta.PLUS_DI(dataframe) # # Minus Directional Indicator / Movement -# dataframe['minus_dm'] = ta.MINUS_DM(dataframe) -# dataframe['minus_di'] = ta.MINUS_DI(dataframe) +# dataframe["minus_dm"] = ta.MINUS_DM(dataframe) +# dataframe["minus_di"] = ta.MINUS_DI(dataframe) # # Aroon, Aroon Oscillator # aroon = ta.AROON(dataframe) -# dataframe['aroonup'] = aroon['aroonup'] -# dataframe['aroondown'] = aroon['aroondown'] -# dataframe['aroonosc'] = ta.AROONOSC(dataframe) +# dataframe["aroonup"] = aroon["aroonup"] +# dataframe["aroondown"] = aroon["aroondown"] +# dataframe["aroonosc"] = ta.AROONOSC(dataframe) # # Awesome Oscillator -# dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) +# dataframe["ao"] = qtpylib.awesome_oscillator(dataframe) # # Keltner Channel # keltner = qtpylib.keltner_channel(dataframe) @@ -36,58 +36,58 @@ dataframe['adx'] = ta.ADX(dataframe) # ) # # Ultimate Oscillator -# dataframe['uo'] = ta.ULTOSC(dataframe) +# dataframe["uo"] = ta.ULTOSC(dataframe) # # Commodity Channel Index: values [Oversold:-100, Overbought:100] -# dataframe['cci'] = ta.CCI(dataframe) +# dataframe["cci"] = ta.CCI(dataframe) # RSI -dataframe['rsi'] = ta.RSI(dataframe) +dataframe["rsi"] = ta.RSI(dataframe) # # Inverse Fisher transform on RSI: values [-1.0, 1.0] (https://goo.gl/2JGGoy) -# rsi = 0.1 * (dataframe['rsi'] - 50) -# dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) +# rsi = 0.1 * (dataframe["rsi"] - 50) +# dataframe["fisher_rsi"] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) # # Inverse Fisher transform on RSI normalized: values [0.0, 100.0] (https://goo.gl/2JGGoy) -# dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) +# dataframe["fisher_rsi_norma"] = 50 * (dataframe["fisher_rsi"] + 1) # # Stochastic Slow # stoch = ta.STOCH(dataframe) -# dataframe['slowd'] = stoch['slowd'] -# dataframe['slowk'] = stoch['slowk'] +# dataframe["slowd"] = stoch["slowd"] +# dataframe["slowk"] = stoch["slowk"] # Stochastic Fast stoch_fast = ta.STOCHF(dataframe) -dataframe['fastd'] = stoch_fast['fastd'] -dataframe['fastk'] = stoch_fast['fastk'] +dataframe["fastd"] = stoch_fast["fastd"] +dataframe["fastk"] = stoch_fast["fastk"] # # Stochastic RSI # Please read https://github.com/freqtrade/freqtrade/issues/2961 before using this. # STOCHRSI is NOT aligned with tradingview, which may result in non-expected results. # stoch_rsi = ta.STOCHRSI(dataframe) -# dataframe['fastd_rsi'] = stoch_rsi['fastd'] -# dataframe['fastk_rsi'] = stoch_rsi['fastk'] +# dataframe["fastd_rsi"] = stoch_rsi["fastd"] +# dataframe["fastk_rsi"] = stoch_rsi["fastk"] # MACD macd = ta.MACD(dataframe) -dataframe['macd'] = macd['macd'] -dataframe['macdsignal'] = macd['macdsignal'] -dataframe['macdhist'] = macd['macdhist'] +dataframe["macd"] = macd["macd"] +dataframe["macdsignal"] = macd["macdsignal"] +dataframe["macdhist"] = macd["macdhist"] # MFI -dataframe['mfi'] = ta.MFI(dataframe) +dataframe["mfi"] = ta.MFI(dataframe) # # ROC -# dataframe['roc'] = ta.ROC(dataframe) +# dataframe["roc"] = ta.ROC(dataframe) # Overlap Studies # ------------------------------------ # Bollinger Bands bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) -dataframe['bb_lowerband'] = bollinger['lower'] -dataframe['bb_middleband'] = bollinger['mid'] -dataframe['bb_upperband'] = bollinger['upper'] +dataframe["bb_lowerband"] = bollinger["lower"] +dataframe["bb_middleband"] = bollinger["mid"] +dataframe["bb_upperband"] = bollinger["upper"] dataframe["bb_percent"] = ( (dataframe["close"] - dataframe["bb_lowerband"]) / (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) @@ -112,95 +112,95 @@ dataframe["bb_width"] = ( # ) # # EMA - Exponential Moving Average -# dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) -# dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) -# dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) -# dataframe['ema21'] = ta.EMA(dataframe, timeperiod=21) -# dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) -# dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) +# dataframe["ema3"] = ta.EMA(dataframe, timeperiod=3) +# dataframe["ema5"] = ta.EMA(dataframe, timeperiod=5) +# dataframe["ema10"] = ta.EMA(dataframe, timeperiod=10) +# dataframe["ema21"] = ta.EMA(dataframe, timeperiod=21) +# dataframe["ema50"] = ta.EMA(dataframe, timeperiod=50) +# dataframe["ema100"] = ta.EMA(dataframe, timeperiod=100) # # SMA - Simple Moving Average -# dataframe['sma3'] = ta.SMA(dataframe, timeperiod=3) -# dataframe['sma5'] = ta.SMA(dataframe, timeperiod=5) -# dataframe['sma10'] = ta.SMA(dataframe, timeperiod=10) -# dataframe['sma21'] = ta.SMA(dataframe, timeperiod=21) -# dataframe['sma50'] = ta.SMA(dataframe, timeperiod=50) -# dataframe['sma100'] = ta.SMA(dataframe, timeperiod=100) +# dataframe["sma3"] = ta.SMA(dataframe, timeperiod=3) +# dataframe["sma5"] = ta.SMA(dataframe, timeperiod=5) +# dataframe["sma10"] = ta.SMA(dataframe, timeperiod=10) +# dataframe["sma21"] = ta.SMA(dataframe, timeperiod=21) +# dataframe["sma50"] = ta.SMA(dataframe, timeperiod=50) +# dataframe["sma100"] = ta.SMA(dataframe, timeperiod=100) # Parabolic SAR -dataframe['sar'] = ta.SAR(dataframe) +dataframe["sar"] = ta.SAR(dataframe) # TEMA - Triple Exponential Moving Average -dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) +dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9) # Cycle Indicator # ------------------------------------ # Hilbert Transform Indicator - SineWave hilbert = ta.HT_SINE(dataframe) -dataframe['htsine'] = hilbert['sine'] -dataframe['htleadsine'] = hilbert['leadsine'] +dataframe["htsine"] = hilbert["sine"] +dataframe["htleadsine"] = hilbert["leadsine"] # Pattern Recognition - Bullish candlestick patterns # ------------------------------------ # # Hammer: values [0, 100] -# dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) +# dataframe["CDLHAMMER"] = ta.CDLHAMMER(dataframe) # # Inverted Hammer: values [0, 100] -# dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) +# dataframe["CDLINVERTEDHAMMER"] = ta.CDLINVERTEDHAMMER(dataframe) # # Dragonfly Doji: values [0, 100] -# dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) +# dataframe["CDLDRAGONFLYDOJI"] = ta.CDLDRAGONFLYDOJI(dataframe) # # Piercing Line: values [0, 100] -# dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100] +# dataframe["CDLPIERCING"] = ta.CDLPIERCING(dataframe) # values [0, 100] # # Morningstar: values [0, 100] -# dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] +# dataframe["CDLMORNINGSTAR"] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] # # Three White Soldiers: values [0, 100] -# dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] +# dataframe["CDL3WHITESOLDIERS"] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] # Pattern Recognition - Bearish candlestick patterns # ------------------------------------ # # Hanging Man: values [0, 100] -# dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) +# dataframe["CDLHANGINGMAN"] = ta.CDLHANGINGMAN(dataframe) # # Shooting Star: values [0, 100] -# dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) +# dataframe["CDLSHOOTINGSTAR"] = ta.CDLSHOOTINGSTAR(dataframe) # # Gravestone Doji: values [0, 100] -# dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) +# dataframe["CDLGRAVESTONEDOJI"] = ta.CDLGRAVESTONEDOJI(dataframe) # # Dark Cloud Cover: values [0, 100] -# dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) +# dataframe["CDLDARKCLOUDCOVER"] = ta.CDLDARKCLOUDCOVER(dataframe) # # Evening Doji Star: values [0, 100] -# dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) +# dataframe["CDLEVENINGDOJISTAR"] = ta.CDLEVENINGDOJISTAR(dataframe) # # Evening Star: values [0, 100] -# dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) +# dataframe["CDLEVENINGSTAR"] = ta.CDLEVENINGSTAR(dataframe) # Pattern Recognition - Bullish/Bearish candlestick patterns # ------------------------------------ # # Three Line Strike: values [0, -100, 100] -# dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) +# dataframe["CDL3LINESTRIKE"] = ta.CDL3LINESTRIKE(dataframe) # # Spinning Top: values [0, -100, 100] -# dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] +# dataframe["CDLSPINNINGTOP"] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] # # Engulfing: values [0, -100, 100] -# dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] +# dataframe["CDLENGULFING"] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] # # Harami: values [0, -100, 100] -# dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] +# dataframe["CDLHARAMI"] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] # # Three Outside Up/Down: values [0, -100, 100] -# dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] +# dataframe["CDL3OUTSIDE"] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] # # Three Inside Up/Down: values [0, -100, 100] -# dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] +# dataframe["CDL3INSIDE"] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] # # Chart type # # ------------------------------------ # # Heikin Ashi Strategy # heikinashi = qtpylib.heikinashi(dataframe) -# dataframe['ha_open'] = heikinashi['open'] -# dataframe['ha_close'] = heikinashi['close'] -# dataframe['ha_high'] = heikinashi['high'] -# dataframe['ha_low'] = heikinashi['low'] +# dataframe["ha_open"] = heikinashi["open"] +# dataframe["ha_close"] = heikinashi["close"] +# dataframe["ha_high"] = heikinashi["high"] +# dataframe["ha_low"] = heikinashi["low"] # Retrieve best bid and best ask from the orderbook # ------------------------------------ """ # first check if dataprovider is available if self.dp: - if self.dp.runmode.value in ('live', 'dry_run'): - ob = self.dp.orderbook(metadata['pair'], 1) - dataframe['best_bid'] = ob['bids'][0][0] - dataframe['best_ask'] = ob['asks'][0][0] + if self.dp.runmode.value in ("live", "dry_run"): + ob = self.dp.orderbook(metadata["pair"], 1) + dataframe["best_bid"] = ob["bids"][0][0] + dataframe["best_ask"] = ob["asks"][0][0] """ diff --git a/freqtrade/templates/strategy_subtemplates/indicators_minimal.j2 b/freqtrade/templates/strategy_subtemplates/indicators_minimal.j2 index 90f4f4d4a..1594a8988 100644 --- a/freqtrade/templates/strategy_subtemplates/indicators_minimal.j2 +++ b/freqtrade/templates/strategy_subtemplates/indicators_minimal.j2 @@ -3,15 +3,15 @@ # ------------------------------------ # RSI -dataframe['rsi'] = ta.RSI(dataframe) +dataframe["rsi"] = ta.RSI(dataframe) # Retrieve best bid and best ask from the orderbook # ------------------------------------ """ # first check if dataprovider is available if self.dp: - if self.dp.runmode.value in ('live', 'dry_run'): - ob = self.dp.orderbook(metadata['pair'], 1) - dataframe['best_bid'] = ob['bids'][0][0] - dataframe['best_ask'] = ob['asks'][0][0] + if self.dp.runmode.value in ("live", "dry_run"): + ob = self.dp.orderbook(metadata["pair"], 1) + dataframe["best_bid"] = ob["bids"][0][0] + dataframe["best_ask"] = ob["asks"][0][0] """ diff --git a/freqtrade/templates/strategy_subtemplates/plot_config_full.j2 b/freqtrade/templates/strategy_subtemplates/plot_config_full.j2 index e3f9e7ca0..08eb3c29f 100644 --- a/freqtrade/templates/strategy_subtemplates/plot_config_full.j2 +++ b/freqtrade/templates/strategy_subtemplates/plot_config_full.j2 @@ -3,18 +3,18 @@ def plot_config(self): return { # Main plot indicators (Moving averages, ...) - 'main_plot': { - 'tema': {}, - 'sar': {'color': 'white'}, + "main_plot": { + "tema": {}, + "sar": {"color": "white"}, }, - 'subplots': { + "subplots": { # Subplots - each dict defines one additional plot "MACD": { - 'macd': {'color': 'blue'}, - 'macdsignal': {'color': 'orange'}, + "macd": {"color": "blue"}, + "macdsignal": {"color": "orange"}, }, "RSI": { - 'rsi': {'color': 'red'}, + "rsi": {"color": "red"}, } } } diff --git a/freqtrade/templates/strategy_subtemplates/sell_trend_full.j2 b/freqtrade/templates/strategy_subtemplates/sell_trend_full.j2 index 3068d8d57..08cb68cd1 100644 --- a/freqtrade/templates/strategy_subtemplates/sell_trend_full.j2 +++ b/freqtrade/templates/strategy_subtemplates/sell_trend_full.j2 @@ -1,3 +1,3 @@ -(qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi -(dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle -(dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling +(qtpylib.crossed_above(dataframe["rsi"], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi +(dataframe["tema"] > dataframe["bb_middleband"]) & # Guard: tema above BB middle +(dataframe["tema"] < dataframe["tema"].shift(1)) & # Guard: tema is falling diff --git a/freqtrade/templates/strategy_subtemplates/sell_trend_minimal.j2 b/freqtrade/templates/strategy_subtemplates/sell_trend_minimal.j2 index 5dabc5910..821b547c3 100644 --- a/freqtrade/templates/strategy_subtemplates/sell_trend_minimal.j2 +++ b/freqtrade/templates/strategy_subtemplates/sell_trend_minimal.j2 @@ -1 +1 @@ -(qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi +(qtpylib.crossed_above(dataframe["rsi"], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi diff --git a/freqtrade/templates/strategy_subtemplates/strategy_attributes_full.j2 b/freqtrade/templates/strategy_subtemplates/strategy_attributes_full.j2 index 86445510d..5ae361996 100644 --- a/freqtrade/templates/strategy_subtemplates/strategy_attributes_full.j2 +++ b/freqtrade/templates/strategy_subtemplates/strategy_attributes_full.j2 @@ -1,13 +1,13 @@ # Optional order type mapping. order_types = { - 'entry': 'limit', - 'exit': 'limit', - 'stoploss': 'market', - 'stoploss_on_exchange': False + "entry": "limit", + "exit": "limit", + "stoploss": "market", + "stoploss_on_exchange": False } # Optional order time in force. order_time_in_force = { - 'entry': 'GTC', - 'exit': 'GTC' + "entry": "GTC", + "exit": "GTC" } From b1ae09c00350bec103f9ba1a8ae6d0cfc3081bfb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Aug 2024 08:37:34 +0200 Subject: [PATCH 182/242] docs: remove callback examples imports --- docs/strategy-callbacks.md | 106 +++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 56 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index a090749cc..8bb3753de 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -198,9 +198,7 @@ Of course, many more things are possible, and all examples can be combined at wi To simulate a regular trailing stoploss of 4% (trailing 4% behind the maximum reached price) you would use the following very simple method: ``` python -# additional imports required -from datetime import datetime -from freqtrade.persistence import Trade +# Default imports class AwesomeStrategy(IStrategy): @@ -208,7 +206,7 @@ class AwesomeStrategy(IStrategy): use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: """ @@ -238,8 +236,7 @@ class AwesomeStrategy(IStrategy): Use the initial stoploss for the first 60 minutes, after this change to 10% trailing stoploss, and after 2 hours (120 minutes) we use a 5% trailing stoploss. ``` python -from datetime import datetime, timedelta -from freqtrade.persistence import Trade +# Default imports class AwesomeStrategy(IStrategy): @@ -247,7 +244,7 @@ class AwesomeStrategy(IStrategy): use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: @@ -265,8 +262,7 @@ Use the initial stoploss for the first 60 minutes, after this change to 10% trai If an additional order fills, set stoploss to -10% below the new `open_rate` ([Averaged across all entries](#position-adjust-calculations)). ``` python -from datetime import datetime, timedelta -from freqtrade.persistence import Trade +# Default imports class AwesomeStrategy(IStrategy): @@ -274,7 +270,7 @@ class AwesomeStrategy(IStrategy): use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: @@ -295,8 +291,7 @@ Use a different stoploss depending on the pair. In this example, we'll trail the highest price with 10% trailing stoploss for `ETH/BTC` and `XRP/BTC`, with 5% trailing stoploss for `LTC/BTC` and with 15% for all other pairs. ``` python -from datetime import datetime -from freqtrade.persistence import Trade +# Default imports class AwesomeStrategy(IStrategy): @@ -304,7 +299,7 @@ class AwesomeStrategy(IStrategy): use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: @@ -322,8 +317,7 @@ Use the initial stoploss until the profit is above 4%, then use a trailing stopl Please note that the stoploss can only increase, values lower than the current stoploss are ignored. ``` python -from datetime import datetime, timedelta -from freqtrade.persistence import Trade +# Default imports class AwesomeStrategy(IStrategy): @@ -331,7 +325,7 @@ class AwesomeStrategy(IStrategy): use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: @@ -355,9 +349,7 @@ Instead of continuously trailing behind the current price, this example sets fix * Once profit is > 40% - set stoploss to 25% above open price. ``` python -from datetime import datetime -from freqtrade.persistence import Trade -from freqtrade.strategy import stoploss_from_open +# Default imports class AwesomeStrategy(IStrategy): @@ -365,7 +357,7 @@ class AwesomeStrategy(IStrategy): use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: @@ -386,6 +378,8 @@ class AwesomeStrategy(IStrategy): Absolute stoploss value may be derived from indicators stored in dataframe. Example uses parabolic SAR below the price as stoploss. ``` python +# Default imports + class AwesomeStrategy(IStrategy): def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -394,7 +388,7 @@ class AwesomeStrategy(IStrategy): use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: @@ -431,10 +425,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela ``` python - - from datetime import datetime - from freqtrade.persistence import Trade - from freqtrade.strategy import IStrategy, stoploss_from_open + # Default imports class AwesomeStrategy(IStrategy): @@ -442,7 +433,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: @@ -475,10 +466,7 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab For futures, we need to adjust the direction (up or down), as well as adjust for leverage, since the [`custom_stoploss`](strategy-callbacks.md#custom-stoploss) callback returns the ["risk for this trade"](stoploss.md#stoploss-and-leverage) - not the relative price movement. ``` python - - from datetime import datetime - from freqtrade.persistence import Trade - from freqtrade.strategy import IStrategy, stoploss_from_absolute, timeframe_to_prev_date + # Default imports class AwesomeStrategy(IStrategy): @@ -488,7 +476,7 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) return dataframe - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) @@ -502,7 +490,6 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab ``` - --- ## Custom order price rules @@ -522,19 +509,18 @@ Each of these methods are called right before placing an order on the exchange. ### Custom order entry and exit price example ``` python -from datetime import datetime, timedelta, timezone -from freqtrade.persistence import Trade +# Default imports class AwesomeStrategy(IStrategy): # ... populate_* methods - def custom_entry_price(self, pair: str, trade: Optional['Trade'], current_time: datetime, proposed_rate: float, + def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime, proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) - new_entryprice = dataframe['bollinger_10_lowerband'].iat[-1] + new_entryprice = dataframe["bollinger_10_lowerband"].iat[-1] return new_entryprice @@ -544,7 +530,7 @@ class AwesomeStrategy(IStrategy): dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) - new_exitprice = dataframe['bollinger_10_upperband'].iat[-1] + new_exitprice = dataframe["bollinger_10_upperband"].iat[-1] return new_exitprice @@ -581,8 +567,7 @@ It applies a tight timeout for higher priced assets, while allowing more time to The function must return either `True` (cancel order) or `False` (keep order alive). ``` python -from datetime import datetime, timedelta -from freqtrade.persistence import Trade, Order + # Default imports class AwesomeStrategy(IStrategy): @@ -594,7 +579,7 @@ class AwesomeStrategy(IStrategy): 'exit': 60 * 25 } - def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', + def check_entry_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): return True @@ -605,7 +590,7 @@ class AwesomeStrategy(IStrategy): return False - def check_exit_timeout(self, pair: str, trade: Trade, order: 'Order', + def check_exit_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): return True @@ -622,8 +607,7 @@ class AwesomeStrategy(IStrategy): ### Custom order timeout example (using additional data) ``` python -from datetime import datetime -from freqtrade.persistence import Trade, Order + # Default imports class AwesomeStrategy(IStrategy): @@ -631,24 +615,24 @@ class AwesomeStrategy(IStrategy): # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours. unfilledtimeout = { - 'entry': 60 * 25, - 'exit': 60 * 25 + "entry": 60 * 25, + "exit": 60 * 25 } - def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', + def check_entry_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: ob = self.dp.orderbook(pair, 1) - current_price = ob['bids'][0][0] + current_price = ob["bids"][0][0] # Cancel buy order if price is more than 2% above the order. if current_price > order.price * 1.02: return True return False - def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order', + def check_exit_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: ob = self.dp.orderbook(pair, 1) - current_price = ob['asks'][0][0] + current_price = ob["asks"][0][0] # Cancel sell order if price is more than 2% below the order. if current_price < order.price * 0.98: return True @@ -667,6 +651,8 @@ This are the last methods that will be called before an order is placed. `confirm_trade_entry()` can be used to abort a trade entry at the latest second (maybe because the price is not what we expect). ``` python +# Default imports + class AwesomeStrategy(IStrategy): # ... populate_* methods @@ -713,8 +699,7 @@ The exit-reasons (if applicable) will be in the following sequence: * `trailing_stop_loss` ``` python -from freqtrade.persistence import Trade - +# Default imports class AwesomeStrategy(IStrategy): @@ -747,7 +732,7 @@ class AwesomeStrategy(IStrategy): :return bool: When True, then the exit-order is placed on the exchange. False aborts the process """ - if exit_reason == 'force_exit' and trade.calc_profit_ratio(rate) < 0: + if exit_reason == "force_exit" and trade.calc_profit_ratio(rate) < 0: # Reject force-sells with negative profit # This is just a sample, please adjust to your needs # (this does not necessarily make sense, assuming you know when you're force-selling) @@ -813,6 +798,7 @@ Returning a value more than the above (so remaining stake_amount would become ne Trades with long duration and 10s or even 100ds of position adjustments are therefore not recommended, and should be closed at regular intervals to not affect performance. ``` python +# Default imports from freqtrade.persistence import Trade from typing import Optional, Tuple, Union @@ -953,8 +939,7 @@ If the cancellation of the original order fails, then the order will not be repl Entry Orders that are cancelled via the above methods will not have this callback called. Be sure to update timeout values to match your expectations. ```python -from freqtrade.persistence import Trade -from datetime import timedelta, datetime +# Default imports class AwesomeStrategy(IStrategy): @@ -985,7 +970,12 @@ class AwesomeStrategy(IStrategy): """ # Limit orders to use and follow SMA200 as price target for the first 10 minutes since entry trigger for BTC/USDT pair. - if pair == 'BTC/USDT' and entry_tag == 'long_sma200' and side == 'long' and (current_time - timedelta(minutes=10)) > trade.open_date_utc: + if ( + pair == "BTC/USDT" + and entry_tag == "long_sma200" + and side == "long" + and (current_time - timedelta(minutes=10)) > trade.open_date_utc + ): # just cancel the order if it has been filled more than half of the amount if order.filled > order.remaining: return None @@ -993,7 +983,7 @@ class AwesomeStrategy(IStrategy): dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) current_candle = dataframe.iloc[-1].squeeze() # desired price - return current_candle['sma_200'] + return current_candle["sma_200"] # default: maintain existing order return current_order_rate ``` @@ -1008,6 +998,8 @@ Values that are above `max_leverage` will be adjusted to `max_leverage`. For markets / exchanges that don't support leverage, this method is ignored. ``` python +# Default imports + class AwesomeStrategy(IStrategy): def leverage(self, pair: str, current_time: datetime, current_rate: float, proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, @@ -1038,6 +1030,8 @@ It will be called independent of the order type (entry, exit, stoploss or positi Assuming that your strategy needs to store the high value of the candle at trade entry, this is possible with this callback as the following example show. ``` python +# Default imports + class AwesomeStrategy(IStrategy): def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None: """ From 768b4e5e2bf89a701b0632eedf5cb2f2eaae940c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Aug 2024 08:38:11 +0200 Subject: [PATCH 183/242] chore: Update formatting of default export sequence --- freqtrade/strategy/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py index d5fb9b7ae..e99473b4e 100644 --- a/freqtrade/strategy/__init__.py +++ b/freqtrade/strategy/__init__.py @@ -1,5 +1,4 @@ # flake8: noqa: F401 - from freqtrade.exchange import ( timeframe_to_minutes, timeframe_to_msecs, @@ -26,20 +25,21 @@ from freqtrade.strategy.strategy_helper import ( # Imports to be used for `from freqtrade.strategy import *` __all__ = [ - "timeframe_to_minutes", - "timeframe_to_next_date", - "timeframe_to_prev_date", - "informative", "IStrategy", "Trade", "Order", "PairLocks", + "informative", # Parameters "BooleanParameter", "CategoricalParameter", "DecimalParameter", "IntParameter", "RealParameter", + # timeframe helpers + "timeframe_to_minutes", + "timeframe_to_next_date", + "timeframe_to_prev_date", # Strategy helper functions "merge_informative_pair", "stoploss_from_absolute", From d754a2e295c0bd0e8efb4ab0cda45cb652393dc7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Aug 2024 08:38:59 +0200 Subject: [PATCH 184/242] feat: improve default imports --- docs/includes/strategy-imports.md | 8 ++++++-- freqtrade/templates/base_strategy.py.j2 | 10 +++++++--- freqtrade/templates/sample_strategy.py | 10 +++++++--- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/includes/strategy-imports.md b/docs/includes/strategy-imports.md index 14dde6c88..63cda329c 100644 --- a/docs/includes/strategy-imports.md +++ b/docs/includes/strategy-imports.md @@ -12,9 +12,9 @@ Obviously you can add more imports as needed for your strategy. # --- Do not remove these imports --- import numpy as np import pandas as pd -from datetime import datetime +from datetime import datetime, timedelta, timezone from pandas import DataFrame -from typing import Dict, Optional, Union +from typing import Dict, Optional, Union, Tuple from freqtrade.strategy import ( IStrategy, @@ -28,6 +28,10 @@ from freqtrade.strategy import ( DecimalParameter, IntParameter, RealParameter, + # timeframe helpers + timeframe_to_minutes, + timeframe_to_next_date, + timeframe_to_prev_date, # Strategy helper functions merge_informative_pair, stoploss_from_absolute, diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index a61093ebd..fe577826a 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -1,12 +1,12 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement # flake8: noqa: F401 # isort: skip_file -# --- Do not remove these libs --- +# --- Do not remove these imports --- import numpy as np import pandas as pd -from datetime import datetime +from datetime import datetime, timedelta, timezone from pandas import DataFrame -from typing import Dict, Optional, Union +from typing import Dict, Optional, Union, Tuple from freqtrade.strategy import ( IStrategy, @@ -20,6 +20,10 @@ from freqtrade.strategy import ( DecimalParameter, IntParameter, RealParameter, + # timeframe helpers + timeframe_to_minutes, + timeframe_to_next_date, + timeframe_to_prev_date, # Strategy helper functions merge_informative_pair, stoploss_from_absolute, diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 3a9235bc2..835e6fa91 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -1,12 +1,12 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement # flake8: noqa: F401 # isort: skip_file -# --- Do not remove these libs --- +# --- Do not remove these imports --- import numpy as np import pandas as pd -from datetime import datetime +from datetime import datetime, timedelta, timezone from pandas import DataFrame -from typing import Dict, Optional, Union +from typing import Dict, Optional, Union, Tuple from freqtrade.strategy import ( IStrategy, @@ -20,6 +20,10 @@ from freqtrade.strategy import ( DecimalParameter, IntParameter, RealParameter, + # timeframe helpers + timeframe_to_minutes, + timeframe_to_next_date, + timeframe_to_prev_date, # Strategy helper functions merge_informative_pair, stoploss_from_absolute, From 7952712c5e11c141089aa244c930f357bd5e6aa1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Aug 2024 08:44:37 +0200 Subject: [PATCH 185/242] chore: update samples to use doublequotes --- docs/strategy-callbacks.md | 77 +++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 8bb3753de..ce1b9907c 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -43,10 +43,10 @@ class AwesomeStrategy(IStrategy): Called only once after bot instantiation. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. """ - if self.config['runmode'].value in ('live', 'dry_run'): + if self.config["runmode"].value in ("live", "dry_run"): # Assign this to the class by using self.* # can then be used by populate_* methods - self.custom_remote_data = requests.get('https://some_remote_source.example.com') + self.custom_remote_data = requests.get("https://some_remote_source.example.com") ``` @@ -59,6 +59,7 @@ seconds, unless configured differently) or once per candle in backtest/hyperopt This can be used to perform calculations which are pair independent (apply to all pairs), loading of external data, etc. ``` python +# Default imports import requests class AwesomeStrategy(IStrategy): @@ -73,10 +74,10 @@ class AwesomeStrategy(IStrategy): :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. """ - if self.config['runmode'].value in ('live', 'dry_run'): + if self.config["runmode"].value in ("live", "dry_run"): # Assign this to the class by using self.* # can then be used by populate_* methods - self.remote_data = requests.get('https://some_remote_source.example.com') + self.remote_data = requests.get("https://some_remote_source.example.com") ``` @@ -85,6 +86,8 @@ class AwesomeStrategy(IStrategy): Called before entering a trade, makes it possible to manage your position size when placing a new trade. ```python +# Default imports + class AwesomeStrategy(IStrategy): def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: Optional[float], max_stake: float, @@ -94,13 +97,13 @@ class AwesomeStrategy(IStrategy): dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) current_candle = dataframe.iloc[-1].squeeze() - if current_candle['fastk_rsi_1h'] > current_candle['fastd_rsi_1h']: - if self.config['stake_amount'] == 'unlimited': + if current_candle["fastk_rsi_1h"] > current_candle["fastd_rsi_1h"]: + if self.config["stake_amount"] == "unlimited": # Use entire available wallet during favorable conditions when in compounding mode. return max_stake else: # Compound profits during favorable conditions instead of using a static stake. - return self.wallets.get_total_stake_amount() / self.config['max_open_trades'] + return self.wallets.get_total_stake_amount() / self.config["max_open_trades"] # Use default stake amount. return proposed_stake @@ -131,25 +134,27 @@ Using `custom_exit()` signals in place of stoploss though *is not recommended*. An example of how we can use different indicators depending on the current profit and also exit trades that were open longer than one day: ``` python +# Default imports + class AwesomeStrategy(IStrategy): - def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, + def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs): dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() # Above 20% profit, sell when rsi < 80 if current_profit > 0.2: - if last_candle['rsi'] < 80: - return 'rsi_below_80' + if last_candle["rsi"] < 80: + return "rsi_below_80" # Between 2% and 10%, sell if EMA-long above EMA-short if 0.02 < current_profit < 0.1: - if last_candle['emalong'] > last_candle['emashort']: - return 'ema_long_below_80' + if last_candle["emalong"] > last_candle["emashort"]: + return "ema_long_below_80" # Sell any positions at a loss if they are held for more than one day. if current_profit < 0.0 and (current_time - trade.open_date_utc).days >= 1: - return 'unclog' + return "unclog" ``` See [Dataframe access](strategy-advanced.md#dataframe-access) for more information about dataframe use in strategy callbacks. @@ -170,7 +175,6 @@ The absolute value of the return value is used (the sign is ignored), so returni Returning `None` will be interpreted as "no desire to change", and is the only safe way to return when you'd like to not modify the stoploss. `NaN` and `inf` values are considered invalid and will be ignored (identical to `None`). - Stoploss on exchange works similar to `trailing_stop`, and the stoploss on exchange is updated as configured in `stoploss_on_exchange_interval` ([More details about stoploss on exchange](stoploss.md#stop-loss-on-exchangefreqtrade)). !!! Note "Use of dates" @@ -303,9 +307,9 @@ class AwesomeStrategy(IStrategy): current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: - if pair in ('ETH/BTC', 'XRP/BTC'): + if pair in ("ETH/BTC", "XRP/BTC"): return -0.10 - elif pair in ('LTC/BTC'): + elif pair in ("LTC/BTC"): return -0.05 return -0.15 ``` @@ -384,7 +388,7 @@ class AwesomeStrategy(IStrategy): def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # <...> - dataframe['sar'] = ta.SAR(dataframe) + dataframe["sar"] = ta.SAR(dataframe) use_custom_stoploss = True @@ -396,7 +400,7 @@ class AwesomeStrategy(IStrategy): last_candle = dataframe.iloc[-1].squeeze() # Use parabolic sar as absolute stoploss price - stoploss_price = last_candle['sar'] + stoploss_price = last_candle["sar"] # Convert absolute price to percentage relative to current_rate if stoploss_price < current_rate: @@ -462,7 +466,7 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab ??? Example "Returning a stoploss using absolute price from the custom stoploss function" - If we want to trail a stop price at 2xATR below current price we can call `stoploss_from_absolute(current_rate + (side * candle['atr'] * 2), current_rate=current_rate, is_short=trade.is_short, leverage=trade.leverage)`. + If we want to trail a stop price at 2xATR below current price we can call `stoploss_from_absolute(current_rate + (side * candle["atr"] * 2), current_rate=current_rate, is_short=trade.is_short, leverage=trade.leverage)`. For futures, we need to adjust the direction (up or down), as well as adjust for leverage, since the [`custom_stoploss`](strategy-callbacks.md#custom-stoploss) callback returns the ["risk for this trade"](stoploss.md#stoploss-and-leverage) - not the relative price movement. ``` python @@ -472,8 +476,8 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab use_custom_stoploss = True - def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe["atr"] = ta.ATR(dataframe, timeperiod=14) return dataframe def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, @@ -483,7 +487,7 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab trade_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc) candle = dataframe.iloc[-1].squeeze() side = 1 if trade.is_short else -1 - return stoploss_from_absolute(current_rate + (side * candle['atr'] * 2), + return stoploss_from_absolute(current_rate + (side * candle["atr"] * 2), current_rate=current_rate, is_short=trade.is_short, leverage=trade.leverage) @@ -575,8 +579,8 @@ class AwesomeStrategy(IStrategy): # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours. unfilledtimeout = { - 'entry': 60 * 25, - 'exit': 60 * 25 + "entry": 60 * 25, + "exit": 60 * 25 } def check_entry_timeout(self, pair: str, trade: Trade, order: Order, @@ -677,7 +681,7 @@ class AwesomeStrategy(IStrategy): :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param current_time: datetime object, containing the current datetime :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. - :param side: 'long' or 'short' - indicating the direction of the proposed trade + :param side: "long" or "short" - indicating the direction of the proposed trade :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the buy-order is placed on the exchange. False aborts the process @@ -725,8 +729,8 @@ class AwesomeStrategy(IStrategy): or current rate for market orders. :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param exit_reason: Exit reason. - Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', - 'exit_signal', 'force_exit', 'emergency_exit'] + Can be any of ["roi", "stop_loss", "stoploss_on_exchange", "trailing_stop_loss", + "exit_signal", "force_exit", "emergency_exit"] :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True, then the exit-order is placed on the exchange. @@ -758,7 +762,7 @@ This callback is **not** called when there is an open order (either buy or sell) `adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible. Position adjustments will always be applied in the direction of the trade, so a positive value will always increase your position (negative values will decrease your position), no matter if it's a long or short trade. -Adjustment orders can be assigned with a tag by returning a 2 element Tuple, with the first element being the adjustment amount, and the 2nd element the tag (e.g. `return 250, 'increase_favorable_conditions'`). +Adjustment orders can be assigned with a tag by returning a 2 element Tuple, with the first element being the adjustment amount, and the 2nd element the tag (e.g. `return 250, "increase_favorable_conditions"`). Modifications to leverage are not possible, and the stake-amount returned is assumed to be before applying leverage. @@ -780,7 +784,7 @@ Returning a value more than the above (so remaining stake_amount would become ne !!! Note "About stake size" Using fixed stake size means it will be the amount used for the first order, just like without position adjustment. If you wish to buy additional orders with DCA, then make sure to leave enough funds in the wallet for that. - Using 'unlimited' stake amount with DCA orders requires you to also implement the `custom_stake_amount()` callback to avoid allocating all funds to the initial order. + Using `"unlimited"` stake amount with DCA orders requires you to also implement the `custom_stake_amount()` callback to avoid allocating all funds to the initial order. !!! Warning "Stoploss calculation" Stoploss is still calculated from the initial opening price, not averaged price. @@ -799,9 +803,6 @@ Returning a value more than the above (so remaining stake_amount would become ne ``` python # Default imports -from freqtrade.persistence import Trade -from typing import Optional, Tuple, Union - class DigDeeperStrategy(IStrategy): @@ -864,7 +865,7 @@ class DigDeeperStrategy(IStrategy): if current_profit > 0.05 and trade.nr_of_successful_exits == 0: # Take half of the profit at +5% - return -(trade.stake_amount / 2), 'half_profit_5%' + return -(trade.stake_amount / 2), "half_profit_5%" if current_profit > -0.05: return None @@ -874,7 +875,7 @@ class DigDeeperStrategy(IStrategy): # Only buy when not actively falling price. last_candle = dataframe.iloc[-1].squeeze() previous_candle = dataframe.iloc[-2].squeeze() - if last_candle['close'] < previous_candle['close']: + if last_candle["close"] < previous_candle["close"]: return None filled_entries = trade.select_filled_orders(trade.entry_side) @@ -892,7 +893,7 @@ class DigDeeperStrategy(IStrategy): stake_amount = filled_entries[0].stake_amount # This then calculates current safety order size stake_amount = stake_amount * (1 + (count_of_entries * 0.25)) - return stake_amount, '1/3rd_increase' + return stake_amount, "1/3rd_increase" except Exception as exception: return None @@ -964,7 +965,7 @@ class AwesomeStrategy(IStrategy): :param proposed_rate: Rate, calculated based on pricing settings in entry_pricing. :param current_order_rate: Rate of the existing order in place. :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. - :param side: 'long' or 'short' - indicating the direction of the proposed trade + :param side: "long" or "short" - indicating the direction of the proposed trade :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: New entry price value if provided @@ -1013,7 +1014,7 @@ class AwesomeStrategy(IStrategy): :param proposed_leverage: A leverage proposed by the bot. :param max_leverage: Max leverage allowed on this pair :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. - :param side: 'long' or 'short' - indicating the direction of the proposed trade + :param side: "long" or "short" - indicating the direction of the proposed trade :return: A leverage amount, which is between 1.0 and max_leverage. """ return 1.0 @@ -1048,7 +1049,7 @@ class AwesomeStrategy(IStrategy): last_candle = dataframe.iloc[-1].squeeze() if (trade.nr_of_successful_entries == 1) and (order.ft_order_side == trade.entry_side): - trade.set_custom_data(key='entry_candle_high', value=last_candle['high']) + trade.set_custom_data(key="entry_candle_high", value=last_candle["high"]) return None From 7cab973cbf3a0283711296d2aae92edc7762dc44 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Aug 2024 11:36:06 +0200 Subject: [PATCH 186/242] feat: get new name for aliased ccxt exchanges --- freqtrade/exchange/exchange_utils.py | 4 ++++ freqtrade/types/valid_exchanges_type.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index 19b33d86c..fc3824dcf 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -2,6 +2,7 @@ Exchange support utils """ +import inspect from datetime import datetime, timedelta, timezone from math import ceil, floor from typing import Any, Dict, List, Optional, Tuple @@ -101,6 +102,9 @@ def _build_exchange_list_entry( "comment": comment, "dex": getattr(ex_mod, "dex", False), "is_alias": getattr(ex_mod, "alias", False), + "alias_for": inspect.getmro(ex_mod.__class__)[1]().id + if getattr(ex_mod, "alias", False) + else None, "trade_modes": [{"trading_mode": "spot", "margin_mode": ""}], } if resolved := exchangeClasses.get(exchange_name.lower()): diff --git a/freqtrade/types/valid_exchanges_type.py b/freqtrade/types/valid_exchanges_type.py index 079b2dc59..497ff8a93 100644 --- a/freqtrade/types/valid_exchanges_type.py +++ b/freqtrade/types/valid_exchanges_type.py @@ -1,5 +1,5 @@ # Used for list-exchanges -from typing import List +from typing import List, Optional from typing_extensions import TypedDict @@ -17,4 +17,5 @@ class ValidExchangesType(TypedDict): comment: str dex: bool is_alias: bool + alias_for: Optional[str] trade_modes: List[TradeModeType] From fd9ec438dc36197b0969aaea380e1b3def143647 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Aug 2024 11:36:34 +0200 Subject: [PATCH 187/242] feat: show name, class name and eventually the replacement alias --- freqtrade/commands/list_commands.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index baa2c8c00..494ee87fa 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -46,14 +46,20 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: table = Table(title=title) table.add_column("Exchange Name") + table.add_column("Class Name") table.add_column("Markets") table.add_column("Reason") for exchange in available_exchanges: - name = Text(exchange["classname"]) + name = Text(exchange["name"]) if exchange["supported"]: - name.append(" (Official)", style="italic") + name.append(" (Supported)", style="italic") name.stylize("green bold") + classname = Text(exchange["classname"]) + if exchange["is_alias"]: + name.stylize("strike") + classname.stylize("strike") + classname.append(f" (use {exchange['alias_for']})", style="italic") trade_modes = Text( ", ".join( @@ -68,6 +74,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: table.add_row( name, + classname, trade_modes, exchange["comment"], style=None if exchange["valid"] else "red", From 7fe23ad8c9cf76c6a0a9ce18020b143540e3753b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Aug 2024 13:15:10 +0200 Subject: [PATCH 188/242] chore: add alias_for to tests --- tests/rpc/test_rpc_apiserver.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 02b8729a6..4f5ef7860 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -2156,6 +2156,7 @@ def test_api_exchanges(botclient): "comment": "", "dex": False, "is_alias": False, + "alias_for": None, "trade_modes": [ {"trading_mode": "spot", "margin_mode": ""}, {"trading_mode": "futures", "margin_mode": "isolated"}, @@ -2171,6 +2172,7 @@ def test_api_exchanges(botclient): "dex": False, "comment": "", "is_alias": False, + "alias_for": None, "trade_modes": [{"trading_mode": "spot", "margin_mode": ""}], } waves = [x for x in response["exchanges"] if x["classname"] == "wavesexchange"][0] @@ -2182,6 +2184,7 @@ def test_api_exchanges(botclient): "dex": True, "comment": ANY, "is_alias": False, + "alias_for": None, "trade_modes": [{"trading_mode": "spot", "margin_mode": ""}], } From 83e0cf75c5d41c65ce23110e20573231278933f0 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sun, 18 Aug 2024 23:48:11 +0900 Subject: [PATCH 189/242] add startup count from strategy to the analysis --- freqtrade/optimize/analysis/recursive.py | 22 +++++++++++++++---- .../optimize/analysis/recursive_helpers.py | 6 ++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/analysis/recursive.py b/freqtrade/optimize/analysis/recursive.py index f6e4fa3a9..e6f7e4152 100644 --- a/freqtrade/optimize/analysis/recursive.py +++ b/freqtrade/optimize/analysis/recursive.py @@ -14,6 +14,7 @@ from freqtrade.loggers.set_log_levels import ( ) from freqtrade.optimize.backtesting import Backtesting from freqtrade.optimize.base_analysis import BaseAnalysis, VarHolder +from freqtrade.resolvers import StrategyResolver logger = logging.getLogger(__name__) @@ -21,10 +22,19 @@ logger = logging.getLogger(__name__) class RecursiveAnalysis(BaseAnalysis): def __init__(self, config: Dict[str, Any], strategy_obj: Dict): - self._startup_candle = config.get("startup_candle", [199, 399, 499, 999, 1999]) + self._startup_candle = list( + map(int, config.get("startup_candle", [199, 399, 499, 999, 1999])) + ) super().__init__(config, strategy_obj) + strat = StrategyResolver.load_strategy(config) + self._strat_scc = strat.startup_candle_count + + if self._strat_scc not in self._startup_candle: + self._startup_candle.append(self._strat_scc) + self._startup_candle.sort() + self.partial_varHolder_array: List[VarHolder] = [] self.partial_varHolder_lookahead_array: List[VarHolder] = [] @@ -58,9 +68,13 @@ class RecursiveAnalysis(BaseAnalysis): values_diff = compare_df.loc[indicator] values_diff_self = values_diff.loc["self"] values_diff_other = values_diff.loc["other"] - diff = (values_diff_other - values_diff_self) / values_diff_self * 100 - self.dict_recursive[indicator][part.startup_candle] = f"{diff:.3f}%" + if values_diff_self and values_diff_other: + diff = (values_diff_other - values_diff_self) / values_diff_self * 100 + str_diff = f"{diff:.3f}%" + else: + str_diff = "NaN" + self.dict_recursive[indicator][part.startup_candle] = str_diff else: logger.info("No variance on indicator(s) found due to recursive formula.") @@ -174,7 +188,7 @@ class RecursiveAnalysis(BaseAnalysis): start_date_partial = end_date_full - timedelta(minutes=int(timeframe_minutes)) for startup_candle in self._startup_candle: - self.fill_partial_varholder(start_date_partial, int(startup_candle)) + self.fill_partial_varholder(start_date_partial, startup_candle) # Restore verbosity, so it's not too quiet for the next strategy restore_verbosity_for_bias_tester() diff --git a/freqtrade/optimize/analysis/recursive_helpers.py b/freqtrade/optimize/analysis/recursive_helpers.py index be596fa68..474604923 100644 --- a/freqtrade/optimize/analysis/recursive_helpers.py +++ b/freqtrade/optimize/analysis/recursive_helpers.py @@ -17,9 +17,13 @@ class RecursiveAnalysisSubFunctions: @staticmethod def text_table_recursive_analysis_instances(recursive_instances: List[RecursiveAnalysis]): startups = recursive_instances[0]._startup_candle + strat_scc = recursive_instances[0]._strat_scc headers = ["Indicators"] for candle in startups: - headers.append(str(candle)) + if candle == strat_scc: + headers.append(f"{candle} (from strategy)") + else: + headers.append(str(candle)) data = [] for inst in recursive_instances: From 95732f4170ad26fd5a6e81d3fcfcff5acd9daca7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 03:48:14 +0000 Subject: [PATCH 190/242] chore(deps): bump cachetools from 5.4.0 to 5.5.0 Bumps [cachetools](https://github.com/tkem/cachetools) from 5.4.0 to 5.5.0. - [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst) - [Commits](https://github.com/tkem/cachetools/compare/v5.4.0...v5.5.0) --- updated-dependencies: - dependency-name: cachetools 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 2b82e72bb..6fac8f9fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ python-telegram-bot==21.4 # can't be hard-pinned due to telegram-bot pinning httpx with ~ httpx>=0.24.1 humanize==4.10.0 -cachetools==5.4.0 +cachetools==5.5.0 requests==2.32.3 urllib3==2.2.2 jsonschema==4.23.0 From b6aa922c098f670551ca27db732c0f8215840264 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 03:48:17 +0000 Subject: [PATCH 191/242] chore(deps): bump markdown from 3.6 to 3.7 Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.6 to 3.7. - [Release notes](https://github.com/Python-Markdown/markdown/releases) - [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md) - [Commits](https://github.com/Python-Markdown/markdown/compare/3.6...3.7) --- updated-dependencies: - dependency-name: markdown 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 b5ce1db14..8409399ae 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,4 @@ -markdown==3.6 +markdown==3.7 mkdocs==1.6.0 mkdocs-material==9.5.31 mdx_truly_sane_lists==1.3 From 8896b0ae7c0e9799636927deba7a6c0c473ca72e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 03:48:31 +0000 Subject: [PATCH 192/242] chore(deps): bump aiohttp from 3.10.3 to 3.10.4 Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.3 to 3.10.4. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.3...v3.10.4) --- updated-dependencies: - dependency-name: aiohttp 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 2b82e72bb..9284771f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ pandas-ta==0.3.14b ccxt==4.3.79 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.0; platform_machine != 'armv7l' -aiohttp==3.10.3 +aiohttp==3.10.4 SQLAlchemy==2.0.32 python-telegram-bot==21.4 # can't be hard-pinned due to telegram-bot pinning httpx with ~ From 314983b139607492e7a7ffd17be38754f37cc659 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 03:48:37 +0000 Subject: [PATCH 193/242] chore(deps): bump tensorboard from 2.17.0 to 2.17.1 Bumps [tensorboard](https://github.com/tensorflow/tensorboard) from 2.17.0 to 2.17.1. - [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.17.0...2.17.1) --- updated-dependencies: - dependency-name: tensorboard dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-freqai.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index a181ab2b4..86cd5152e 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -11,5 +11,5 @@ catboost==1.2.5; 'arm' not in platform_machine matplotlib==3.9.1.post1 lightgbm==4.5.0 xgboost==2.0.3 -tensorboard==2.17.0 +tensorboard==2.17.1 datasieve==0.1.7 From a266997b6988e66b2c7e1bad156974946ab5ffba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 03:49:02 +0000 Subject: [PATCH 194/242] chore(deps-dev): bump ruff from 0.5.7 to 0.6.1 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.7 to 0.6.1. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.5.7...0.6.1) --- updated-dependencies: - dependency-name: ruff 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 0a725f660..e5191d48d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==4.0.1 -ruff==0.5.7 +ruff==0.6.1 mypy==1.11.1 pre-commit==3.8.0 pytest==8.3.2 From ba3223a9a38e67914108214124baf57716052e9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 03:49:05 +0000 Subject: [PATCH 195/242] chore(deps): bump tables from 3.9.1 to 3.10.1 Bumps [tables](https://github.com/PyTables/PyTables) from 3.9.1 to 3.10.1. - [Release notes](https://github.com/PyTables/PyTables/releases) - [Changelog](https://github.com/PyTables/PyTables/blob/master/RELEASE_NOTES.rst) - [Commits](https://github.com/PyTables/PyTables/compare/v3.9.1...v3.10.1) --- updated-dependencies: - dependency-name: tables 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 2b82e72bb..214a434e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ technical==1.4.4 tabulate==0.9.0 pycoingecko==3.1.0 jinja2==3.1.4 -tables==3.9.1 +tables==3.10.1 joblib==1.4.2 rich==13.7.1 pyarrow==17.0.0; platform_machine != 'armv7l' From 8321425e62282e4b467162e0daab1042975d435f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 03:49:10 +0000 Subject: [PATCH 196/242] chore(deps): bump uvicorn from 0.30.5 to 0.30.6 Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.30.5 to 0.30.6. - [Release notes](https://github.com/encode/uvicorn/releases) - [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md) - [Commits](https://github.com/encode/uvicorn/compare/0.30.5...0.30.6) --- updated-dependencies: - dependency-name: uvicorn 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 2b82e72bb..d9313dc28 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,7 +41,7 @@ sdnotify==0.3.2 # API Server fastapi==0.112.0 pydantic==2.8.2 -uvicorn==0.30.5 +uvicorn==0.30.6 pyjwt==2.9.0 aiofiles==24.1.0 psutil==6.0.0 From 75714ae84a624357cc1e20c585c5bce60c1cdd5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 06:00:50 +0000 Subject: [PATCH 197/242] chore(deps): bump matplotlib from 3.9.1.post1 to 3.9.2 Bumps [matplotlib](https://github.com/matplotlib/matplotlib) from 3.9.1.post1 to 3.9.2. - [Release notes](https://github.com/matplotlib/matplotlib/releases) - [Commits](https://github.com/matplotlib/matplotlib/compare/v3.9.1.post1...v3.9.2) --- updated-dependencies: - dependency-name: matplotlib dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-freqai.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index 86cd5152e..0db247289 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -8,7 +8,7 @@ joblib==1.4.2 catboost==1.2.5; 'arm' not in platform_machine # Pin Matplotlib - it's depended on by catboost # Temporary downgrade of matplotlib due to https://github.com/matplotlib/matplotlib/issues/28551 -matplotlib==3.9.1.post1 +matplotlib==3.9.2 lightgbm==4.5.0 xgboost==2.0.3 tensorboard==2.17.1 From b859d7f3a551f2c07dc206e5c51a783355ea8adf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 06:00:52 +0000 Subject: [PATCH 198/242] chore(deps): bump ccxt from 4.3.79 to 4.3.84 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.79 to 4.3.84. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/4.3.79...4.3.84) --- 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 9284771f7..271d9b2d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ bottleneck==1.4.0 numexpr==2.10.1 pandas-ta==0.3.14b -ccxt==4.3.79 +ccxt==4.3.84 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.0; platform_machine != 'armv7l' aiohttp==3.10.4 From a70116ed4d771d0a20ce434c788dadef495f943f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 06:50:46 +0000 Subject: [PATCH 199/242] chore(deps): bump fastapi from 0.112.0 to 0.112.1 Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.112.0 to 0.112.1. - [Release notes](https://github.com/fastapi/fastapi/releases) - [Commits](https://github.com/fastapi/fastapi/compare/0.112.0...0.112.1) --- updated-dependencies: - dependency-name: fastapi 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 adac223af..c7e71426f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,7 +39,7 @@ orjson==3.10.7 sdnotify==0.3.2 # API Server -fastapi==0.112.0 +fastapi==0.112.1 pydantic==2.8.2 uvicorn==0.30.6 pyjwt==2.9.0 From bc719feb5dd245f68b75cf22c0abadae50b8fa91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 16:19:33 +0000 Subject: [PATCH 200/242] chore(deps): bump mkdocs-material in the mkdocs group Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material). Updates `mkdocs-material` from 9.5.31 to 9.5.32 - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.31...9.5.32) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch dependency-group: mkdocs ... 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 8409399ae..1d29ef619 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.7 mkdocs==1.6.0 -mkdocs-material==9.5.31 +mkdocs-material==9.5.32 mdx_truly_sane_lists==1.3 pymdown-extensions==10.9 jinja2==3.1.4 From 986ff7d1b195245b3e4f11f0735e4e8c890e0ab2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Aug 2024 18:19:42 +0200 Subject: [PATCH 201/242] chore: rename parameter to avoid naming collision --- freqtrade/rpc/api_server/api_ws.py | 2 +- freqtrade/rpc/api_server/ws/channel.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/api_server/api_ws.py b/freqtrade/rpc/api_server/api_ws.py index 5e2eddc68..ed458165e 100644 --- a/freqtrade/rpc/api_server/api_ws.py +++ b/freqtrade/rpc/api_server/api_ws.py @@ -58,7 +58,7 @@ async def channel_broadcaster(channel: WebSocketChannel, message_stream: Message " consumers." ) - await channel.send(message, timeout=True) + await channel.send(message, use_timeout=True) async def _process_consumer_request(request: Dict[str, Any], channel: WebSocketChannel, rpc: RPC): diff --git a/freqtrade/rpc/api_server/ws/channel.py b/freqtrade/rpc/api_server/ws/channel.py index 0041bb6b2..3c1e0ce2d 100644 --- a/freqtrade/rpc/api_server/ws/channel.py +++ b/freqtrade/rpc/api_server/ws/channel.py @@ -80,7 +80,7 @@ class WebSocketChannel: self._send_high_limit = min(max(self.avg_send_time * 2, 1), 3) async def send( - self, message: Union[WSMessageSchemaType, Dict[str, Any]], timeout: bool = False + self, message: Union[WSMessageSchemaType, Dict[str, Any]], use_timeout: bool = False ): """ Send a message on the wrapped websocket. If the sending @@ -88,7 +88,7 @@ class WebSocketChannel: disconnect the connection. :param message: The message to send - :param timeout: Enforce send high limit, defaults to False + :param use_timeout: Enforce send high limit, defaults to False """ try: _ = time.time() @@ -96,7 +96,8 @@ class WebSocketChannel: # a TimeoutError and bubble up to the # message_endpoint to close the connection await asyncio.wait_for( - self._wrapped_ws.send(message), timeout=self._send_high_limit if timeout else None + self._wrapped_ws.send(message), + timeout=self._send_high_limit if use_timeout else None, ) total_time = time.time() - _ self._send_times.append(total_time) From 4d175a466efd01db1d2b6bfa9dc84a87ecb04117 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 16:20:09 +0000 Subject: [PATCH 202/242] chore(deps): bump ccxt from 4.3.84 to 4.3.85 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.84 to 4.3.85. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/4.3.84...4.3.85) --- 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 bd41dbc7e..45adfa3ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ bottleneck==1.4.0 numexpr==2.10.1 pandas-ta==0.3.14b -ccxt==4.3.84 +ccxt==4.3.85 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.0; platform_machine != 'armv7l' aiohttp==3.10.4 From 976f9b259044e114ba220ac3b94066aa8ffa41ea Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Aug 2024 18:23:36 +0200 Subject: [PATCH 203/242] chore: re-format ipynb notebook --- docs/strategy_analysis_example.md | 44 +++++++++++++------ .../templates/strategy_analysis_example.ipynb | 42 +++++++++++++----- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index 22828b899..30faa27ad 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -13,6 +13,7 @@ Please follow the [documentation](https://www.freqtrade.io/en/stable/data-downlo import os from pathlib import Path + # Change directory # Modify this cell to insure that the output shows the correct path. # Define all paths relative to the project root shown in the cell output @@ -20,12 +21,14 @@ project_root = "somedir/freqtrade" i=0 try: os.chdir(project_root) - assert Path('LICENSE').is_file() -except: - while i<4 and (not Path('LICENSE').is_file()): - os.chdir(Path(Path.cwd(), '../')) - i+=1 - project_root = Path.cwd() + if not Path('LICENSE').is_file(): + i = 0 + while i < 4 and (not Path('LICENSE').is_file()): + os.chdir(Path(Path.cwd(), '../')) + i += 1 + project_root = Path.cwd() +except FileNotFoundError: + print("Please define the project root relative to the current directory") print(Path.cwd()) ``` @@ -35,6 +38,7 @@ print(Path.cwd()) ```python from freqtrade.configuration import Configuration + # Customize these according to your needs. # Initialize empty configuration object @@ -58,6 +62,7 @@ pair = "BTC/USDT" from freqtrade.data.history import load_pair_history from freqtrade.enums import CandleType + candles = load_pair_history(datadir=data_location, timeframe=config["timeframe"], pair=pair, @@ -76,8 +81,10 @@ candles.head() ```python # Load strategy using values set above -from freqtrade.resolvers import StrategyResolver from freqtrade.data.dataprovider import DataProvider +from freqtrade.resolvers import StrategyResolver + + strategy = StrategyResolver.load_strategy(config) strategy.dp = DataProvider(config, None, None) strategy.ft_bot_start() @@ -119,10 +126,13 @@ Analyze a trades dataframe (also used below for plotting) ```python from freqtrade.data.btanalysis import load_backtest_data, load_backtest_stats + # if backtest_dir points to a directory, it'll automatically load the last backtest file. backtest_dir = config["user_data_dir"] / "backtest_results" -# backtest_dir can also point to a specific file -# backtest_dir = config["user_data_dir"] / "backtest_results/backtest-result-2020-07-01_20-04-22.json" +# backtest_dir can also point to a specific file +# backtest_dir = ( +# config["user_data_dir"] / "backtest_results/backtest-result-2020-07-01_20-04-22.json" +# ) ``` @@ -132,7 +142,8 @@ backtest_dir = config["user_data_dir"] / "backtest_results" stats = load_backtest_stats(backtest_dir) strategy = 'SampleStrategy' -# All statistics are available per strategy, so if `--strategy-list` was used during backtest, this will be reflected here as well. +# All statistics are available per strategy, so if `--strategy-list` was used during backtest, +# this will be reflected here as well. # Example usages: print(stats['strategy'][strategy]['results_per_pair']) # Get pairlist used for this backtest @@ -166,10 +177,12 @@ trades.groupby("pair")["exit_reason"].value_counts() ```python # Plotting equity line (starting with 0 on day 1 and adding daily profit for each backtested day) +import pandas as pd +import plotly.express as px + from freqtrade.configuration import Configuration from freqtrade.data.btanalysis import load_backtest_stats -import plotly.express as px -import pandas as pd + # strategy = 'SampleStrategy' # config = Configuration.from_files(["user_data/config.json"]) @@ -194,6 +207,7 @@ In case you did already some trading and want to analyze your performance ```python from freqtrade.data.btanalysis import load_trades_from_db + # Fetch trades from database trades = load_trades_from_db("sqlite:///tradesv3.sqlite") @@ -210,6 +224,7 @@ This can be useful to find the best `max_open_trades` parameter, when used with ```python from freqtrade.data.btanalysis import analyze_trade_parallelism + # Analyze the above parallel_trades = analyze_trade_parallelism(trades, '5m') @@ -222,7 +237,9 @@ Freqtrade offers interactive plotting capabilities based on plotly. ```python -from freqtrade.plot.plotting import generate_candlestick_graph +from freqtrade.plot.plotting import generate_candlestick_graph + + # Limit graph period to keep plotly quick and reactive # Filter trades to one pair @@ -257,6 +274,7 @@ graph.show(renderer="browser") ```python import plotly.figure_factory as ff + hist_data = [trades.profit_ratio] group_labels = ['profit_ratio'] # name of the dataset diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb index 8d4459a3c..d7008eca9 100644 --- a/freqtrade/templates/strategy_analysis_example.ipynb +++ b/freqtrade/templates/strategy_analysis_example.ipynb @@ -29,6 +29,7 @@ "import os\n", "from pathlib import Path\n", "\n", + "\n", "# Change directory\n", "# Modify this cell to insure that the output shows the correct path.\n", "# Define all paths relative to the project root shown in the cell output\n", @@ -36,12 +37,14 @@ "i=0\n", "try:\n", " os.chdir(project_root)\n", - " assert Path('LICENSE').is_file()\n", - "except:\n", - " while i<4 and (not Path('LICENSE').is_file()):\n", - " os.chdir(Path(Path.cwd(), '../'))\n", - " i+=1\n", - " project_root = Path.cwd()\n", + " if not Path('LICENSE').is_file():\n", + " i = 0\n", + " while i < 4 and (not Path('LICENSE').is_file()):\n", + " os.chdir(Path(Path.cwd(), '../'))\n", + " i += 1\n", + " project_root = Path.cwd()\n", + "except FileNotFoundError:\n", + " print(\"Please define the project root relative to the current directory\")\n", "print(Path.cwd())" ] }, @@ -60,6 +63,7 @@ "source": [ "from freqtrade.configuration import Configuration\n", "\n", + "\n", "# Customize these according to your needs.\n", "\n", "# Initialize empty configuration object\n", @@ -87,6 +91,7 @@ "from freqtrade.data.history import load_pair_history\n", "from freqtrade.enums import CandleType\n", "\n", + "\n", "candles = load_pair_history(datadir=data_location,\n", " timeframe=config[\"timeframe\"],\n", " pair=pair,\n", @@ -114,8 +119,10 @@ "outputs": [], "source": [ "# Load strategy using values set above\n", - "from freqtrade.resolvers import StrategyResolver\n", "from freqtrade.data.dataprovider import DataProvider\n", + "from freqtrade.resolvers import StrategyResolver\n", + "\n", + "\n", "strategy = StrategyResolver.load_strategy(config)\n", "strategy.dp = DataProvider(config, None, None)\n", "strategy.ft_bot_start()\n", @@ -179,10 +186,13 @@ "source": [ "from freqtrade.data.btanalysis import load_backtest_data, load_backtest_stats\n", "\n", + "\n", "# if backtest_dir points to a directory, it'll automatically load the last backtest file.\n", "backtest_dir = config[\"user_data_dir\"] / \"backtest_results\"\n", "# backtest_dir can also point to a specific file\n", - "# backtest_dir = config[\"user_data_dir\"] / \"backtest_results/backtest-result-2020-07-01_20-04-22.json\"" + "# backtest_dir = (\n", + "# config[\"user_data_dir\"] / \"backtest_results/backtest-result-2020-07-01_20-04-22.json\"\n", + "# )" ] }, { @@ -196,7 +206,8 @@ "stats = load_backtest_stats(backtest_dir)\n", "\n", "strategy = 'SampleStrategy'\n", - "# All statistics are available per strategy, so if `--strategy-list` was used during backtest, this will be reflected here as well.\n", + "# All statistics are available per strategy, so if `--strategy-list` was used during backtest,\n", + "# this will be reflected here as well.\n", "# Example usages:\n", "print(stats['strategy'][strategy]['results_per_pair'])\n", "# Get pairlist used for this backtest\n", @@ -242,10 +253,12 @@ "source": [ "# Plotting equity line (starting with 0 on day 1 and adding daily profit for each backtested day)\n", "\n", + "import pandas as pd\n", + "import plotly.express as px\n", + "\n", "from freqtrade.configuration import Configuration\n", "from freqtrade.data.btanalysis import load_backtest_stats\n", - "import plotly.express as px\n", - "import pandas as pd\n", + "\n", "\n", "# strategy = 'SampleStrategy'\n", "# config = Configuration.from_files([\"user_data/config.json\"])\n", @@ -278,6 +291,7 @@ "source": [ "from freqtrade.data.btanalysis import load_trades_from_db\n", "\n", + "\n", "# Fetch trades from database\n", "trades = load_trades_from_db(\"sqlite:///tradesv3.sqlite\")\n", "\n", @@ -303,6 +317,7 @@ "source": [ "from freqtrade.data.btanalysis import analyze_trade_parallelism\n", "\n", + "\n", "# Analyze the above\n", "parallel_trades = analyze_trade_parallelism(trades, '5m')\n", "\n", @@ -324,7 +339,9 @@ "metadata": {}, "outputs": [], "source": [ - "from freqtrade.plot.plotting import generate_candlestick_graph\n", + "from freqtrade.plot.plotting import generate_candlestick_graph\n", + "\n", + "\n", "# Limit graph period to keep plotly quick and reactive\n", "\n", "# Filter trades to one pair\n", @@ -370,6 +387,7 @@ "source": [ "import plotly.figure_factory as ff\n", "\n", + "\n", "hist_data = [trades.profit_ratio]\n", "group_labels = ['profit_ratio'] # name of the dataset\n", "\n", From d2c908b1ab09e0fbc97d65d01ccbe8217936c55b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Aug 2024 18:23:53 +0200 Subject: [PATCH 204/242] chore: bump ruff pre-commit version --- .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 7bf5de208..b9c116885 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.5.7' + rev: 'v0.6.1' hooks: - id: ruff From ce66fbb59513a1c4ff68727bb83333b50564b874 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Aug 2024 19:59:15 +0200 Subject: [PATCH 205/242] chore: ruff format notebook --- docs/strategy_analysis_example.md | 75 +++++++++--------- .../templates/strategy_analysis_example.ipynb | 76 +++++++++---------- 2 files changed, 73 insertions(+), 78 deletions(-) diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index 30faa27ad..9f37b2975 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -18,13 +18,13 @@ from pathlib import Path # Modify this cell to insure that the output shows the correct path. # Define all paths relative to the project root shown in the cell output project_root = "somedir/freqtrade" -i=0 +i = 0 try: os.chdir(project_root) - if not Path('LICENSE').is_file(): + if not Path("LICENSE").is_file(): i = 0 - while i < 4 and (not Path('LICENSE').is_file()): - os.chdir(Path(Path.cwd(), '../')) + while i < 4 and (not Path("LICENSE").is_file()): + os.chdir(Path(Path.cwd(), "../")) i += 1 project_root = Path.cwd() except FileNotFoundError: @@ -63,12 +63,13 @@ from freqtrade.data.history import load_pair_history from freqtrade.enums import CandleType -candles = load_pair_history(datadir=data_location, - timeframe=config["timeframe"], - pair=pair, - data_format = "json", # Make sure to update this to your data - candle_type=CandleType.SPOT, - ) +candles = load_pair_history( + datadir=data_location, + timeframe=config["timeframe"], + pair=pair, + data_format="json", # Make sure to update this to your data + candle_type=CandleType.SPOT, +) # Confirm success print(f"Loaded {len(candles)} rows of data for {pair} from {data_location}") @@ -90,7 +91,7 @@ strategy.dp = DataProvider(config, None, None) strategy.ft_bot_start() # Generate buy/sell signals using strategy -df = strategy.analyze_ticker(candles, {'pair': pair}) +df = strategy.analyze_ticker(candles, {"pair": pair}) df.tail() ``` @@ -109,7 +110,7 @@ df.tail() ```python # Report results print(f"Generated {df['enter_long'].sum()} entry signals") -data = df.set_index('date', drop=False) +data = df.set_index("date", drop=False) data.tail() ``` @@ -141,25 +142,24 @@ backtest_dir = config["user_data_dir"] / "backtest_results" # This contains all information used to generate the backtest result. stats = load_backtest_stats(backtest_dir) -strategy = 'SampleStrategy' +strategy = "SampleStrategy" # All statistics are available per strategy, so if `--strategy-list` was used during backtest, # this will be reflected here as well. # Example usages: -print(stats['strategy'][strategy]['results_per_pair']) +print(stats["strategy"][strategy]["results_per_pair"]) # Get pairlist used for this backtest -print(stats['strategy'][strategy]['pairlist']) +print(stats["strategy"][strategy]["pairlist"]) # Get market change (average change of all pairs from start to end of the backtest period) -print(stats['strategy'][strategy]['market_change']) +print(stats["strategy"][strategy]["market_change"]) # Maximum drawdown () -print(stats['strategy'][strategy]['max_drawdown']) +print(stats["strategy"][strategy]["max_drawdown"]) # Maximum drawdown start and end -print(stats['strategy'][strategy]['drawdown_start']) -print(stats['strategy'][strategy]['drawdown_end']) +print(stats["strategy"][strategy]["drawdown_start"]) +print(stats["strategy"][strategy]["drawdown_end"]) # Get strategy comparison (only relevant if multiple strategies were compared) -print(stats['strategy_comparison']) - +print(stats["strategy_comparison"]) ``` @@ -189,14 +189,13 @@ from freqtrade.data.btanalysis import load_backtest_stats # backtest_dir = config["user_data_dir"] / "backtest_results" stats = load_backtest_stats(backtest_dir) -strategy_stats = stats['strategy'][strategy] +strategy_stats = stats["strategy"][strategy] -df = pd.DataFrame(columns=['dates','equity'], data=strategy_stats['daily_profit']) -df['equity_daily'] = df['equity'].cumsum() +df = pd.DataFrame(columns=["dates", "equity"], data=strategy_stats["daily_profit"]) +df["equity_daily"] = df["equity"].cumsum() fig = px.line(df, x="dates", y="equity_daily") fig.show() - ``` ### Load live trading results into a pandas dataframe @@ -226,7 +225,7 @@ from freqtrade.data.btanalysis import analyze_trade_parallelism # Analyze the above -parallel_trades = analyze_trade_parallelism(trades, '5m') +parallel_trades = analyze_trade_parallelism(trades, "5m") parallel_trades.plot() ``` @@ -243,19 +242,17 @@ from freqtrade.plot.plotting import generate_candlestick_graph # Limit graph period to keep plotly quick and reactive # Filter trades to one pair -trades_red = trades.loc[trades['pair'] == pair] +trades_red = trades.loc[trades["pair"] == pair] -data_red = data['2019-06-01':'2019-06-10'] +data_red = data["2019-06-01":"2019-06-10"] # Generate candlestick graph -graph = generate_candlestick_graph(pair=pair, - data=data_red, - trades=trades_red, - indicators1=['sma20', 'ema50', 'ema55'], - indicators2=['rsi', 'macd', 'macdsignal', 'macdhist'] - ) - - - +graph = generate_candlestick_graph( + pair=pair, + data=data_red, + trades=trades_red, + indicators1=["sma20", "ema50", "ema55"], + indicators2=["rsi", "macd", "macdsignal", "macdhist"], +) ``` @@ -265,7 +262,6 @@ graph = generate_candlestick_graph(pair=pair, # Render graph in a separate window graph.show(renderer="browser") - ``` ## Plot average profit per trade as distribution graph @@ -276,11 +272,10 @@ import plotly.figure_factory as ff hist_data = [trades.profit_ratio] -group_labels = ['profit_ratio'] # name of the dataset +group_labels = ["profit_ratio"] # name of the dataset fig = ff.create_distplot(hist_data, group_labels, bin_size=0.01) fig.show() - ``` Feel free to submit an issue or Pull Request enhancing this document if you would like to share ideas on how to best analyze the data. diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb index d7008eca9..e81ff72ca 100644 --- a/freqtrade/templates/strategy_analysis_example.ipynb +++ b/freqtrade/templates/strategy_analysis_example.ipynb @@ -34,13 +34,13 @@ "# Modify this cell to insure that the output shows the correct path.\n", "# Define all paths relative to the project root shown in the cell output\n", "project_root = \"somedir/freqtrade\"\n", - "i=0\n", + "i = 0\n", "try:\n", " os.chdir(project_root)\n", - " if not Path('LICENSE').is_file():\n", + " if not Path(\"LICENSE\").is_file():\n", " i = 0\n", - " while i < 4 and (not Path('LICENSE').is_file()):\n", - " os.chdir(Path(Path.cwd(), '../'))\n", + " while i < 4 and (not Path(\"LICENSE\").is_file()):\n", + " os.chdir(Path(Path.cwd(), \"../\"))\n", " i += 1\n", " project_root = Path.cwd()\n", "except FileNotFoundError:\n", @@ -92,12 +92,13 @@ "from freqtrade.enums import CandleType\n", "\n", "\n", - "candles = load_pair_history(datadir=data_location,\n", - " timeframe=config[\"timeframe\"],\n", - " pair=pair,\n", - " data_format = \"json\", # Make sure to update this to your data\n", - " candle_type=CandleType.SPOT,\n", - " )\n", + "candles = load_pair_history(\n", + " datadir=data_location,\n", + " timeframe=config[\"timeframe\"],\n", + " pair=pair,\n", + " data_format=\"json\", # Make sure to update this to your data\n", + " candle_type=CandleType.SPOT,\n", + ")\n", "\n", "# Confirm success\n", "print(f\"Loaded {len(candles)} rows of data for {pair} from {data_location}\")\n", @@ -128,7 +129,7 @@ "strategy.ft_bot_start()\n", "\n", "# Generate buy/sell signals using strategy\n", - "df = strategy.analyze_ticker(candles, {'pair': pair})\n", + "df = strategy.analyze_ticker(candles, {\"pair\": pair})\n", "df.tail()" ] }, @@ -155,7 +156,7 @@ "source": [ "# Report results\n", "print(f\"Generated {df['enter_long'].sum()} entry signals\")\n", - "data = df.set_index('date', drop=False)\n", + "data = df.set_index(\"date\", drop=False)\n", "data.tail()" ] }, @@ -205,24 +206,24 @@ "# This contains all information used to generate the backtest result.\n", "stats = load_backtest_stats(backtest_dir)\n", "\n", - "strategy = 'SampleStrategy'\n", + "strategy = \"SampleStrategy\"\n", "# All statistics are available per strategy, so if `--strategy-list` was used during backtest,\n", "# this will be reflected here as well.\n", "# Example usages:\n", - "print(stats['strategy'][strategy]['results_per_pair'])\n", + "print(stats[\"strategy\"][strategy][\"results_per_pair\"])\n", "# Get pairlist used for this backtest\n", - "print(stats['strategy'][strategy]['pairlist'])\n", + "print(stats[\"strategy\"][strategy][\"pairlist\"])\n", "# Get market change (average change of all pairs from start to end of the backtest period)\n", - "print(stats['strategy'][strategy]['market_change'])\n", + "print(stats[\"strategy\"][strategy][\"market_change\"])\n", "# Maximum drawdown ()\n", - "print(stats['strategy'][strategy]['max_drawdown'])\n", + "print(stats[\"strategy\"][strategy][\"max_drawdown\"])\n", "# Maximum drawdown start and end\n", - "print(stats['strategy'][strategy]['drawdown_start'])\n", - "print(stats['strategy'][strategy]['drawdown_end'])\n", + "print(stats[\"strategy\"][strategy][\"drawdown_start\"])\n", + "print(stats[\"strategy\"][strategy][\"drawdown_end\"])\n", "\n", "\n", "# Get strategy comparison (only relevant if multiple strategies were compared)\n", - "print(stats['strategy_comparison'])\n" + "print(stats[\"strategy_comparison\"])" ] }, { @@ -265,13 +266,13 @@ "# backtest_dir = config[\"user_data_dir\"] / \"backtest_results\"\n", "\n", "stats = load_backtest_stats(backtest_dir)\n", - "strategy_stats = stats['strategy'][strategy]\n", + "strategy_stats = stats[\"strategy\"][strategy]\n", "\n", - "df = pd.DataFrame(columns=['dates','equity'], data=strategy_stats['daily_profit'])\n", - "df['equity_daily'] = df['equity'].cumsum()\n", + "df = pd.DataFrame(columns=[\"dates\", \"equity\"], data=strategy_stats[\"daily_profit\"])\n", + "df[\"equity_daily\"] = df[\"equity\"].cumsum()\n", "\n", "fig = px.line(df, x=\"dates\", y=\"equity_daily\")\n", - "fig.show()\n" + "fig.show()" ] }, { @@ -319,7 +320,7 @@ "\n", "\n", "# Analyze the above\n", - "parallel_trades = analyze_trade_parallelism(trades, '5m')\n", + "parallel_trades = analyze_trade_parallelism(trades, \"5m\")\n", "\n", "parallel_trades.plot()" ] @@ -345,18 +346,17 @@ "# Limit graph period to keep plotly quick and reactive\n", "\n", "# Filter trades to one pair\n", - "trades_red = trades.loc[trades['pair'] == pair]\n", + "trades_red = trades.loc[trades[\"pair\"] == pair]\n", "\n", - "data_red = data['2019-06-01':'2019-06-10']\n", + "data_red = data[\"2019-06-01\":\"2019-06-10\"]\n", "# Generate candlestick graph\n", - "graph = generate_candlestick_graph(pair=pair,\n", - " data=data_red,\n", - " trades=trades_red,\n", - " indicators1=['sma20', 'ema50', 'ema55'],\n", - " indicators2=['rsi', 'macd', 'macdsignal', 'macdhist']\n", - " )\n", - "\n", - "\n" + "graph = generate_candlestick_graph(\n", + " pair=pair,\n", + " data=data_red,\n", + " trades=trades_red,\n", + " indicators1=[\"sma20\", \"ema50\", \"ema55\"],\n", + " indicators2=[\"rsi\", \"macd\", \"macdsignal\", \"macdhist\"],\n", + ")" ] }, { @@ -369,7 +369,7 @@ "# graph.show()\n", "\n", "# Render graph in a separate window\n", - "graph.show(renderer=\"browser\")\n" + "graph.show(renderer=\"browser\")" ] }, { @@ -389,10 +389,10 @@ "\n", "\n", "hist_data = [trades.profit_ratio]\n", - "group_labels = ['profit_ratio'] # name of the dataset\n", + "group_labels = [\"profit_ratio\"] # name of the dataset\n", "\n", "fig = ff.create_distplot(hist_data, group_labels, bin_size=0.01)\n", - "fig.show()\n" + "fig.show()" ] }, { From 6bd21b8995485d0a6b0d413f999b9a61238e64ea Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Aug 2024 20:01:19 +0200 Subject: [PATCH 206/242] chore: pin tables for python 3.9 --- requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 214a434e1..7e34690eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,9 @@ technical==1.4.4 tabulate==0.9.0 pycoingecko==3.1.0 jinja2==3.1.4 -tables==3.10.1 +# Tables 3.10 dropped support for Python 3.9 +tables==3.9.1; python_version < "3.10" +tables==3.10.1; python_version >= "3.10" joblib==1.4.2 rich==13.7.1 pyarrow==17.0.0; platform_machine != 'armv7l' From f4440d43def4e69fe01ff41f744c492d00e8462e Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 20 Aug 2024 06:45:09 +0200 Subject: [PATCH 207/242] chore: increase wait time on ws to avoid flukes --- tests/exchange_online/test_ccxt_ws_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange_online/test_ccxt_ws_compat.py b/tests/exchange_online/test_ccxt_ws_compat.py index 78e6e317b..8083a926c 100644 --- a/tests/exchange_online/test_ccxt_ws_compat.py +++ b/tests/exchange_online/test_ccxt_ws_compat.py @@ -32,7 +32,7 @@ class TestCCXTExchangeWs: while True: # Don't start the test if we are too close to the end of the minute. - if dt_now().second < 50 and dt_now().second != 0: + if dt_now().second < 50 and dt_now().second > 1: break sleep(1) From c7485e3fd48351085f46a8216a4347b1cb6b908c Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 20 Aug 2024 20:17:31 +0200 Subject: [PATCH 208/242] chore: add mike to mkdocs config --- docs/requirements-docs.txt | 1 + mkdocs.yml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 1d29ef619..14f30e10f 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -4,3 +4,4 @@ mkdocs-material==9.5.32 mdx_truly_sane_lists==1.3 pymdown-extensions==10.9 jinja2==3.1.4 +mike==2.1.3 diff --git a/mkdocs.yml b/mkdocs.yml index 62274daa9..11c452def 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -112,3 +112,9 @@ markdown_extensions: custom_checkbox: true - pymdownx.tilde - mdx_truly_sane_lists +extra: + version: + provider: mike +plugins: + - mike: + deploy_prefix: 'en' From e05a6e976e781eaf9e74e6d82a2fb4d18e412358 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 20 Aug 2024 20:20:25 +0200 Subject: [PATCH 209/242] chore: add Ci for gha deployment --- .github/workflows/deploy-docs.yml | 64 +++++++++++++++++++++++++++++ .github/workflows/deploy-to-gha.yml | 43 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 .github/workflows/deploy-docs.yml create mode 100644 .github/workflows/deploy-to-gha.yml diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 000000000..68503c507 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,64 @@ +name: Build Documentation + +on: + push: + branches: + - develop + release: + types: [published] + + +# disable permissions for all of the available permissions +permissions: {} + + +jobs: + build-docs: + permissions: + contents: write # for mike to push + name: Deploy Docs through mike + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r docs/requirements-docs.txt + + - name: Fetch gh-pages branch + run: | + git fetch origin gh-pages --depth=1 + + - name: Configure Git user + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + + - name: Build and push Mike + if: ${{ github.event_name == 'push' }} + run: | + mike deploy ${{ github.ref_name }} latest --push --update-aliases + + - name: Build and push Mike - Release + if: ${{ github.event_name == 'release' }} + run: | + mike deploy ${{ github.ref_name }} stable --push --update-aliases + + - name: Show mike versions + run: | + mike list + + deploy-to-gha: + needs: build-docs + permissions: + contents: read + pages: write + id-token: write + uses: ./.github/workflows/deploy-to-gha.yml + diff --git a/.github/workflows/deploy-to-gha.yml b/.github/workflows/deploy-to-gha.yml new file mode 100644 index 000000000..8fe77edad --- /dev/null +++ b/.github/workflows/deploy-to-gha.yml @@ -0,0 +1,43 @@ +name: Deploy Documentation + +on: + workflow_dispatch: + workflow_call: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: gh-pages + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: '.' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From a7e2bf071be2a5a8a33949978365d33d36ba8895 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 20 Aug 2024 20:32:53 +0200 Subject: [PATCH 210/242] chore: Move deployment to gh native actions --- .github/workflows/deploy-docs.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 68503c507..4fa1412dd 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -54,11 +54,11 @@ jobs: run: | mike list - deploy-to-gha: - needs: build-docs - permissions: - contents: read - pages: write - id-token: write - uses: ./.github/workflows/deploy-to-gha.yml + # deploy-to-gha: + # needs: build-docs + # permissions: + # contents: read + # pages: write + # id-token: write + # uses: ./.github/workflows/deploy-to-gha.yml From 19ccb27dbd81ba71e69730b7e32474b48ab824dd Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 20 Aug 2024 21:19:26 +0200 Subject: [PATCH 211/242] chore: deploy through github internal pipeline --- .github/workflows/deploy-docs.yml | 9 ------ .github/workflows/deploy-to-gha.yml | 43 ----------------------------- 2 files changed, 52 deletions(-) delete mode 100644 .github/workflows/deploy-to-gha.yml diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 4fa1412dd..c97934a51 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -53,12 +53,3 @@ jobs: - name: Show mike versions run: | mike list - - # deploy-to-gha: - # needs: build-docs - # permissions: - # contents: read - # pages: write - # id-token: write - # uses: ./.github/workflows/deploy-to-gha.yml - diff --git a/.github/workflows/deploy-to-gha.yml b/.github/workflows/deploy-to-gha.yml deleted file mode 100644 index 8fe77edad..000000000 --- a/.github/workflows/deploy-to-gha.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Deploy Documentation - -on: - workflow_dispatch: - workflow_call: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - # Single deploy job since we're just deploying - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: gh-pages - - - name: Setup Pages - uses: actions/configure-pages@v5 - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - # Upload entire repository - path: '.' - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 From 68be56240dacdb0ebcc56ec4dbbfcd09ea8ec89c Mon Sep 17 00:00:00 2001 From: colorfulgray0 Date: Wed, 21 Aug 2024 17:46:58 +0800 Subject: [PATCH 212/242] chore: fix test param --- tests/exchange/test_okx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange/test_okx.py b/tests/exchange/test_okx.py index df428010f..97f8a3a4c 100644 --- a/tests/exchange/test_okx.py +++ b/tests/exchange/test_okx.py @@ -640,7 +640,7 @@ def test__get_stop_params_okx(mocker, default_conf): default_conf["trading_mode"] = "futures" default_conf["margin_mode"] = "isolated" exchange = get_patched_exchange(mocker, default_conf, exchange="okx") - params = exchange._get_stop_params("ETH/USDT:USDT", 1500, "sell") + params = exchange._get_stop_params("sell", "market", 1500) assert params["tdMode"] == "isolated" assert params["posSide"] == "net" From 4a621996827b640e43c62c887dc48070f96245ae Mon Sep 17 00:00:00 2001 From: Robert Davey Date: Wed, 21 Aug 2024 15:26:19 +0100 Subject: [PATCH 213/242] Add clarification for untradeable pairs vs markets --- docs/utils.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/utils.md b/docs/utils.md index 78688a7f7..26c8b06d4 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -420,6 +420,10 @@ Common arguments: By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded on the exchange. The see the list of all pairs/markets (not only the active ones), use the `-a`/`-all` option. +Pairs may be listed as untradeable if: + +* the exchange precisionMode is set to tick size (see https://github.com/ccxt/ccxt/wiki/Manual#precision-mode) +* the smallest tradeable price for the market is very small, i.e. less than `1e-11` (`0.00000000001`) Pairs/markets are sorted by its symbol string in the printed output. From 33614d8ff08b8e9a6e51b09d5f19e8654e94fead Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 21 Aug 2024 19:51:24 +0200 Subject: [PATCH 214/242] docs: Improve wording for untradeable pairs --- docs/utils.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index 26c8b06d4..5be380b40 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -418,12 +418,9 @@ Common arguments: ``` -By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded -on the exchange. The see the list of all pairs/markets (not only the active ones), use the `-a`/`-all` option. -Pairs may be listed as untradeable if: - -* the exchange precisionMode is set to tick size (see https://github.com/ccxt/ccxt/wiki/Manual#precision-mode) -* the smallest tradeable price for the market is very small, i.e. less than `1e-11` (`0.00000000001`) +By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded on the exchange. +You can use the `-a`/`-all` option to see the list of all pairs/markets, including the inactive ones. +Pairs may be listed as untradeable if the smallest tradeable price for the market is very small, i.e. less than `1e-11` (`0.00000000001`) Pairs/markets are sorted by its symbol string in the printed output. From fd30edf2bb8c748db17815ffe7ff270faff270d2 Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Thu, 22 Aug 2024 03:13:36 +0000 Subject: [PATCH 215/242] chore: update pre-commit hooks --- .../exchange/binance_leverage_tiers.json | 784 +++++++++++++++++- 1 file changed, 782 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index ca672a9a8..5b29aadc5 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -8273,6 +8273,136 @@ } } ], + "BRETT/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "5000", + "maintMarginRatio": "0.02", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "125.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "750.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10750.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "20750.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83250.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333250.0" + } + } + ], "BSV/USDT:USDT": [ { "tier": 1.0, @@ -15548,10 +15678,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxLeverage": 21.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "21", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.015", @@ -15965,6 +16095,136 @@ } } ], + "G/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "5000", + "maintMarginRatio": "0.02", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "125.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "750.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10750.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "20750.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83250.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333250.0" + } + } + ], "GALA/USDT:USDT": [ { "tier": 1.0, @@ -28361,6 +28621,136 @@ } } ], + "RARE/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "5000", + "maintMarginRatio": "0.02", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "125.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "750.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10750.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "20750.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83250.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333250.0" + } + } + ], "RAY/USDT:USDT": [ { "tier": 1.0, @@ -33015,6 +33405,266 @@ } } ], + "SYN/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "5000", + "maintMarginRatio": "0.02", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "125.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "750.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10750.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "20750.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83250.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333250.0" + } + } + ], + "SYS/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "5000", + "maintMarginRatio": "0.02", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "125.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "750.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10750.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "20750.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83250.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333250.0" + } + } + ], "T/USDT:USDT": [ { "tier": 1.0, @@ -35633,6 +36283,136 @@ } } ], + "VOXEL/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "5000", + "maintMarginRatio": "0.02", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "125.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "750.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10750.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "20750.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83250.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333250.0" + } + } + ], "W/USDT:USDT": [ { "tier": 1.0, From 01b7ad4a3f6d4bacd089c7dabeeedea51cfc9beb Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 23 Aug 2024 18:16:01 +0200 Subject: [PATCH 216/242] feat: prevent freqAI startup on exchanges without history closes #10570 --- freqtrade/exchange/exchange.py | 8 ++++++++ tests/exchange/test_exchange.py | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 9e58acc27..eb842e39a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -337,6 +337,7 @@ class Exchange: self.validate_pricing(config["exit_pricing"]) self.validate_pricing(config["entry_pricing"]) self.validate_orderflow(config["exchange"]) + self.validate_freqai(config) def _init_ccxt( self, exchange_config: Dict[str, Any], sync: bool, ccxt_kwargs: Dict[str, Any] @@ -826,6 +827,13 @@ class Exchange: f"Trade data not available for {self.name}. Can't use orderflow feature." ) + def validate_freqai(self, config: Config) -> None: + freqai_enabled = config.get("freqai", {}).get("enabled", False) + if freqai_enabled and not self._ft_has["ohlcv_has_history"]: + raise ConfigurationError( + f"Historic OHLCV data not available for {self.name}. Can't use freqAI." + ) + def validate_required_startup_candles(self, startup_candles: int, timeframe: str) -> int: """ Checks if required startup_candles is more than ohlcv_candle_limit(). diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 4a1865658..1e43cf51b 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -342,6 +342,27 @@ def test_validate_orderflow(default_conf, mocker, caplog): ex.validate_orderflow({"use_public_trades": True}) +def test_validate_freqai_compat(default_conf, mocker, caplog): + caplog.set_level(logging.INFO) + # Test kraken - as it doesn't support historic trades data. + ex = get_patched_exchange(mocker, default_conf, exchange="kraken") + mocker.patch(f"{EXMS}.exchange_has", return_value=True) + + default_conf["freqai"] = {"enabled": False} + ex.validate_freqai(default_conf) + + default_conf["freqai"] = {"enabled": True} + with pytest.raises(ConfigurationError, match=r"Historic OHLCV data not available for.*"): + ex.validate_freqai(default_conf) + + # Binance supports historic data. + ex = get_patched_exchange(mocker, default_conf, exchange="binance") + default_conf["freqai"] = {"enabled": True} + ex.validate_freqai(default_conf) + default_conf["freqai"] = {"enabled": False} + ex.validate_freqai(default_conf) + + @pytest.mark.parametrize( "price,precision_mode,precision,expected", [ From e87927564b3b49c4a21c0e2d059d9d7a27e6f220 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 23 Aug 2024 18:18:05 +0200 Subject: [PATCH 217/242] chore: Improve typing --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index eb842e39a..48e800d35 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -323,7 +323,7 @@ class Exchange: asyncio.set_event_loop(loop) return loop - def validate_config(self, config): + def validate_config(self, config: Config) -> None: # Check if timeframe is available self.validate_timeframes(config.get("timeframe")) From bcae1dce7ba1d7d73513b74a75c00cd1aef268a6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 24 Aug 2024 09:19:07 +0200 Subject: [PATCH 218/242] docs: reduce font-weight of version_list --- docs/stylesheets/ft.extra.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/stylesheets/ft.extra.css b/docs/stylesheets/ft.extra.css index 3369fa177..18985baf0 100644 --- a/docs/stylesheets/ft.extra.css +++ b/docs/stylesheets/ft.extra.css @@ -11,3 +11,7 @@ .rst-versions .rst-other-versions { color: white; } + +.md-version__list { + font-weight: 500 !important; +} From d1bc51959939059e0d099702e13ddf108fac1a26 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 24 Aug 2024 09:33:33 +0200 Subject: [PATCH 219/242] docs: Show version alias on versions --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 11c452def..a9b86b4fd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -115,6 +115,7 @@ markdown_extensions: extra: version: provider: mike + alias: true plugins: - mike: deploy_prefix: 'en' From 1b7056853b12f5534f65d205174efca1b6e486cc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 24 Aug 2024 18:06:08 +0200 Subject: [PATCH 220/242] refactor: move test_liquidation_price_binance to binance test file --- tests/exchange/test_binance.py | 105 ++++++++++++++++++++++++++++++++ tests/exchange/test_exchange.py | 105 -------------------------------- 2 files changed, 105 insertions(+), 105 deletions(-) diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index a5f9ea654..0ae1d6e20 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -170,6 +170,111 @@ def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side): assert not exchange.stoploss_adjust(sl2, order, side=side) +@pytest.mark.parametrize( + "exchange_name, is_short, trading_mode, margin_mode, wallet_balance, " + "mm_ex_1, upnl_ex_1, maintenance_amt, amount, open_rate, " + "mm_ratio, expected", + [ + ( + "binance", + False, + "futures", + "isolated", + 1535443.01, + 0.0, + 0.0, + 135365.00, + 3683.979, + 1456.84, + 0.10, + 1114.78, + ), + ( + "binance", + False, + "futures", + "isolated", + 1535443.01, + 0.0, + 0.0, + 16300.000, + 109.488, + 32481.980, + 0.025, + 18778.73, + ), + ( + "binance", + False, + "futures", + "cross", + 1535443.01, + 71200.81144, + -56354.57, + 135365.00, + 3683.979, + 1456.84, + 0.10, + 1153.26, + ), + ( + "binance", + False, + "futures", + "cross", + 1535443.01, + 356512.508, + -448192.89, + 16300.000, + 109.488, + 32481.980, + 0.025, + 26316.89, + ), + ], +) +def test_liquidation_price_binance( + mocker, + default_conf, + exchange_name, + open_rate, + is_short, + trading_mode, + margin_mode, + wallet_balance, + mm_ex_1, + upnl_ex_1, + maintenance_amt, + amount, + mm_ratio, + expected, +): + default_conf["trading_mode"] = trading_mode + default_conf["margin_mode"] = margin_mode + default_conf["liquidation_buffer"] = 0.0 + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) + exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(mm_ratio, maintenance_amt)) + assert ( + pytest.approx( + round( + exchange.get_liquidation_price( + pair="DOGE/USDT", + open_rate=open_rate, + is_short=is_short, + wallet_balance=wallet_balance, + mm_ex_1=mm_ex_1, + upnl_ex_1=upnl_ex_1, + amount=amount, + stake_amount=open_rate * amount, + leverage=5, + ), + 2, + ) + ) + == expected + ) + + def test_fill_leverage_tiers_binance(default_conf, mocker): api_mock = MagicMock() api_mock.fetch_leverage_tiers = MagicMock( diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 1e43cf51b..2e99ddcde 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -5667,111 +5667,6 @@ def test_liquidation_price_is_none( ) -@pytest.mark.parametrize( - "exchange_name, is_short, trading_mode, margin_mode, wallet_balance, " - "mm_ex_1, upnl_ex_1, maintenance_amt, amount, open_rate, " - "mm_ratio, expected", - [ - ( - "binance", - False, - "futures", - "isolated", - 1535443.01, - 0.0, - 0.0, - 135365.00, - 3683.979, - 1456.84, - 0.10, - 1114.78, - ), - ( - "binance", - False, - "futures", - "isolated", - 1535443.01, - 0.0, - 0.0, - 16300.000, - 109.488, - 32481.980, - 0.025, - 18778.73, - ), - ( - "binance", - False, - "futures", - "cross", - 1535443.01, - 71200.81144, - -56354.57, - 135365.00, - 3683.979, - 1456.84, - 0.10, - 1153.26, - ), - ( - "binance", - False, - "futures", - "cross", - 1535443.01, - 356512.508, - -448192.89, - 16300.000, - 109.488, - 32481.980, - 0.025, - 26316.89, - ), - ], -) -def test_liquidation_price_binance( - mocker, - default_conf, - exchange_name, - open_rate, - is_short, - trading_mode, - margin_mode, - wallet_balance, - mm_ex_1, - upnl_ex_1, - maintenance_amt, - amount, - mm_ratio, - expected, -): - default_conf["trading_mode"] = trading_mode - default_conf["margin_mode"] = margin_mode - default_conf["liquidation_buffer"] = 0.0 - exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) - exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(mm_ratio, maintenance_amt)) - assert ( - pytest.approx( - round( - exchange.get_liquidation_price( - pair="DOGE/USDT", - open_rate=open_rate, - is_short=is_short, - wallet_balance=wallet_balance, - mm_ex_1=mm_ex_1, - upnl_ex_1=upnl_ex_1, - amount=amount, - stake_amount=open_rate * amount, - leverage=5, - ), - 2, - ) - ) - == expected - ) - - def test_get_max_pair_stake_amount( mocker, default_conf, From 5cca19bb83b432549dffd863853dae7bf86375df Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 24 Aug 2024 18:07:19 +0200 Subject: [PATCH 221/242] refactor: simplify binance liquidation test setup --- tests/exchange/test_binance.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index 0ae1d6e20..60d0ff556 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -171,12 +171,11 @@ def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side): @pytest.mark.parametrize( - "exchange_name, is_short, trading_mode, margin_mode, wallet_balance, " + "is_short, trading_mode, margin_mode, wallet_balance, " "mm_ex_1, upnl_ex_1, maintenance_amt, amount, open_rate, " "mm_ratio, expected", [ ( - "binance", False, "futures", "isolated", @@ -190,7 +189,6 @@ def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side): 1114.78, ), ( - "binance", False, "futures", "isolated", @@ -204,7 +202,6 @@ def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side): 18778.73, ), ( - "binance", False, "futures", "cross", @@ -218,7 +215,6 @@ def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side): 1153.26, ), ( - "binance", False, "futures", "cross", @@ -236,7 +232,6 @@ def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side): def test_liquidation_price_binance( mocker, default_conf, - exchange_name, open_rate, is_short, trading_mode, @@ -252,7 +247,7 @@ def test_liquidation_price_binance( default_conf["trading_mode"] = trading_mode default_conf["margin_mode"] = margin_mode default_conf["liquidation_buffer"] = 0.0 - exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange="binance") exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(mm_ratio, maintenance_amt)) assert ( pytest.approx( From 6235b50c9d1b2e704bade5d889c8e367866aa12f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 03:59:43 +0000 Subject: [PATCH 222/242] chore(deps-dev): bump the types group with 2 updates Bumps the types group with 2 updates: [types-cachetools](https://github.com/python/typeshed) and [types-python-dateutil](https://github.com/python/typeshed). Updates `types-cachetools` from 5.4.0.20240717 to 5.5.0.20240820 - [Commits](https://github.com/python/typeshed/commits) Updates `types-python-dateutil` from 2.9.0.20240316 to 2.9.0.20240821 - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-cachetools dependency-type: direct:development update-type: version-update:semver-minor dependency-group: types - dependency-name: types-python-dateutil dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index e5191d48d..b706532dc 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -25,8 +25,8 @@ time-machine==2.15.0 nbconvert==7.16.4 # mypy types -types-cachetools==5.4.0.20240717 +types-cachetools==5.5.0.20240820 types-filelock==3.2.7 types-requests==2.32.0.20240712 types-tabulate==0.9.0.20240106 -types-python-dateutil==2.9.0.20240316 +types-python-dateutil==2.9.0.20240821 From 0076205da6e19324473bae286bcce0ddfb550814 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 04:00:21 +0000 Subject: [PATCH 223/242] chore(deps-dev): bump pytest-asyncio in the pytest group Bumps the pytest group with 1 update: [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio). Updates `pytest-asyncio` from 0.23.8 to 0.24.0 - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.23.8...v0.24.0) --- updated-dependencies: - dependency-name: pytest-asyncio dependency-type: direct:development update-type: version-update:semver-minor dependency-group: pytest ... 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 e5191d48d..e9dfab426 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,7 +11,7 @@ ruff==0.6.1 mypy==1.11.1 pre-commit==3.8.0 pytest==8.3.2 -pytest-asyncio==0.23.8 +pytest-asyncio==0.24.0 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-random-order==1.1.1 From 24785d28e6fc0d08c94f76c1e799c3334280fbd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 04:00:35 +0000 Subject: [PATCH 224/242] chore(deps): bump mkdocs-material in the mkdocs group Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material). Updates `mkdocs-material` from 9.5.32 to 9.5.33 - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.32...9.5.33) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch dependency-group: mkdocs ... 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 14f30e10f..91a2ad768 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.7 mkdocs==1.6.0 -mkdocs-material==9.5.32 +mkdocs-material==9.5.33 mdx_truly_sane_lists==1.3 pymdown-extensions==10.9 jinja2==3.1.4 From f1f4ed97ca1a92acda0d4dac12fa53a9719dc443 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 04:00:46 +0000 Subject: [PATCH 225/242] chore(deps-dev): bump ruff from 0.6.1 to 0.6.2 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.1 to 0.6.2. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.6.1...0.6.2) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index e5191d48d..71e26257e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==4.0.1 -ruff==0.6.1 +ruff==0.6.2 mypy==1.11.1 pre-commit==3.8.0 pytest==8.3.2 From ba2cf8015b0afe62051fbd9a9c7cfc8889df3d32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 04:00:51 +0000 Subject: [PATCH 226/242] chore(deps): bump websockets from 12.0 to 13.0 Bumps [websockets](https://github.com/python-websockets/websockets) from 12.0 to 13.0. - [Release notes](https://github.com/python-websockets/websockets/releases) - [Commits](https://github.com/python-websockets/websockets/compare/12.0...13.0) --- updated-dependencies: - dependency-name: websockets dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fde00dd91..a94796526 100644 --- a/requirements.txt +++ b/requirements.txt @@ -59,7 +59,7 @@ pytz==2024.1 schedule==1.2.2 #WS Messages -websockets==12.0 +websockets==13.0 janus==1.0.0 ast-comments==1.2.2 From ca0be181bc0a236b23df741412b77ba311f231b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 04:00:57 +0000 Subject: [PATCH 227/242] chore(deps): bump ccxt from 4.3.85 to 4.3.88 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.85 to 4.3.88. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/4.3.85...4.3.88) --- 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 fde00dd91..01288542c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ bottleneck==1.4.0 numexpr==2.10.1 pandas-ta==0.3.14b -ccxt==4.3.85 +ccxt==4.3.88 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.0; platform_machine != 'armv7l' aiohttp==3.10.4 From 877c6635e4b8106afa7d8a1fae6238a71e204d43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 04:01:13 +0000 Subject: [PATCH 228/242] chore(deps): bump scipy from 1.14.0 to 1.14.1 Bumps [scipy](https://github.com/scipy/scipy) from 1.14.0 to 1.14.1. - [Release notes](https://github.com/scipy/scipy/releases) - [Commits](https://github.com/scipy/scipy/compare/v1.14.0...v1.14.1) --- updated-dependencies: - dependency-name: scipy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 6475525de..3391d8c68 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -2,7 +2,7 @@ -r requirements.txt # Required for hyperopt -scipy==1.14.0; python_version >= "3.10" +scipy==1.14.1; python_version >= "3.10" scipy==1.13.1; python_version < "3.10" scikit-learn==1.5.1 ft-scikit-optimize==0.9.2 From 6d280be081dd052927e587f96d2b12d735da3cc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 04:01:39 +0000 Subject: [PATCH 229/242] chore(deps): bump fastapi from 0.112.1 to 0.112.2 Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.112.1 to 0.112.2. - [Release notes](https://github.com/fastapi/fastapi/releases) - [Commits](https://github.com/fastapi/fastapi/compare/0.112.1...0.112.2) --- updated-dependencies: - dependency-name: fastapi 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 fde00dd91..538cb30ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,7 +41,7 @@ orjson==3.10.7 sdnotify==0.3.2 # API Server -fastapi==0.112.1 +fastapi==0.112.2 pydantic==2.8.2 uvicorn==0.30.6 pyjwt==2.9.0 From a9451a5413ab26beca8459c6eca7bace7ece0485 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 06:40:25 +0000 Subject: [PATCH 230/242] chore(deps-dev): bump mypy from 1.11.1 to 1.11.2 Bumps [mypy](https://github.com/python/mypy) from 1.11.1 to 1.11.2. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.11.1...v1.11.2) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index af1f8730e..f3a7b8016 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,7 +8,7 @@ coveralls==4.0.1 ruff==0.6.2 -mypy==1.11.1 +mypy==1.11.2 pre-commit==3.8.0 pytest==8.3.2 pytest-asyncio==0.24.0 From eaf68fe1051aac3b2175461b461f8b547c1ebd77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 07:29:04 +0000 Subject: [PATCH 231/242] chore(deps): bump aiohttp from 3.10.4 to 3.10.5 Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.4 to 3.10.5. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.4...v3.10.5) --- updated-dependencies: - dependency-name: aiohttp 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 01288542c..94e360790 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ pandas-ta==0.3.14b ccxt==4.3.88 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.0; platform_machine != 'armv7l' -aiohttp==3.10.4 +aiohttp==3.10.5 SQLAlchemy==2.0.32 python-telegram-bot==21.4 # can't be hard-pinned due to telegram-bot pinning httpx with ~ From ec55fdb8d830d2779758f572103bb0655afa19dc Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Tue, 27 Aug 2024 03:02:41 +0000 Subject: [PATCH 232/242] chore: update pre-commit hooks --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b9c116885..cef9f1af0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: # stages: [push] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.11.1" + rev: "v1.11.2" hooks: - id: mypy exclude: build_helpers @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.6.1' + rev: 'v0.6.2' hooks: - id: ruff From 660a5d910a067be6e9413a8c4a91b30830e44843 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 27 Aug 2024 19:44:09 +0200 Subject: [PATCH 233/242] chore: bump pre-commit type deps --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b9c116885..66edba544 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,11 +14,11 @@ repos: - id: mypy exclude: build_helpers additional_dependencies: - - types-cachetools==5.4.0.20240717 + - types-cachetools==5.5.0.20240820 - types-filelock==3.2.7 - types-requests==2.32.0.20240712 - types-tabulate==0.9.0.20240106 - - types-python-dateutil==2.9.0.20240316 + - types-python-dateutil==2.9.0.20240821 - SQLAlchemy==2.0.32 # stages: [push] From 655a300acb25a3c6686d3dc5e04f42fe0663287e Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 28 Aug 2024 20:27:16 +0200 Subject: [PATCH 234/242] docs: re-establish search box on develop documentation --- mkdocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index a9b86b4fd..9e67f1f71 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -117,5 +117,7 @@ extra: provider: mike alias: true plugins: + - search: + enabled: true - mike: deploy_prefix: 'en' From 4c487d666f6038e95aa62c2f7aaa35c7c0bd08a5 Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Thu, 29 Aug 2024 03:12:45 +0000 Subject: [PATCH 235/242] chore: update pre-commit hooks --- .../exchange/binance_leverage_tiers.json | 2606 ++++++++++++----- 1 file changed, 1862 insertions(+), 744 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 5b29aadc5..303b66eb8 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -638,13 +638,13 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, + "maxNotional": 20000.0, "maintenanceMarginRate": 0.0065, "maxLeverage": 75.0, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "5000", + "notionalCap": "20000", "notionalFloor": "0", "maintMarginRatio": "0.0065", "cum": "0.0" @@ -653,129 +653,129 @@ { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, + "minNotional": 20000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "25000", - "notionalFloor": "5000", + "notionalCap": "200000", + "notionalFloor": "20000", "maintMarginRatio": "0.01", - "cum": "17.5" + "cum": "70.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 150000.0, + "minNotional": 200000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "150000", - "notionalFloor": "25000", + "notionalCap": "1000000", + "notionalFloor": "200000", "maintMarginRatio": "0.02", - "cum": "267.5" + "cum": "2070.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 300000.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "300000", - "notionalFloor": "150000", + "notionalCap": "2000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.025", - "cum": "1017.5" + "cum": "7070.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 3000000.0, + "minNotional": 2000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "3000000", - "notionalFloor": "300000", + "notionalCap": "10000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.05", - "cum": "8517.5" + "cum": "57070.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 6000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "6000000", - "notionalFloor": "3000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.1", - "cum": "158517.5" + "cum": "557070.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 7500000.0, + "minNotional": 20000000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "7500000", - "notionalFloor": "6000000", + "notionalCap": "25000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.125", - "cum": "308517.5" + "cum": "1057070.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 15000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "15000000", - "notionalFloor": "7500000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.25", - "cum": "1246017.5" + "cum": "4182070.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 15000000.0, - "maxNotional": 30000000.0, + "minNotional": 50000000.0, + "maxNotional": 100000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "15000000", + "notionalCap": "100000000", + "notionalFloor": "50000000", "maintMarginRatio": "0.5", - "cum": "4996017.5" + "cum": "16682070.0" } } ], @@ -914,128 +914,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 10000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" + "initialLeverage": "50", + "notionalCap": "60000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 80000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 60000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "80000", - "notionalFloor": "25000", - "maintMarginRatio": "0.025", - "cum": "150.0" + "initialLeverage": "25", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.02", + "cum": "350.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 800000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "800000", - "notionalFloor": "80000", - "maintMarginRatio": "0.05", - "cum": "2150.0" + "initialLeverage": "20", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.025", + "cum": "1850.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 1600000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 600000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "1600000", - "notionalFloor": "800000", - "maintMarginRatio": "0.1", - "cum": "42150.0" + "initialLeverage": "10", + "notionalCap": "3000000", + "notionalFloor": "600000", + "maintMarginRatio": "0.05", + "cum": "16850.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1600000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "2000000", - "notionalFloor": "1600000", - "maintMarginRatio": "0.125", - "cum": "82150.0" + "initialLeverage": "5", + "notionalCap": "6000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.1", + "cum": "166850.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 6000000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "4000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.25", - "cum": "332150.0" + "initialLeverage": "4", + "notionalCap": "7500000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.125", + "cum": "316850.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 8000000.0, + "minNotional": 7500000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "15000000", + "notionalFloor": "7500000", + "maintMarginRatio": "0.25", + "cum": "1254350.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 15000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "8000000", - "notionalFloor": "4000000", + "notionalCap": "30000000", + "notionalFloor": "15000000", "maintMarginRatio": "0.5", - "cum": "1332150.0" + "cum": "5004350.0" } } ], @@ -2811,6 +2827,152 @@ } } ], + "ALPACA/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "40000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "175.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.05", + "cum": "1175.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "11175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "21175.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83675.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333675.0" + } + } + ], "ALPHA/USDT:USDT": [ { "tier": 1.0, @@ -7562,13 +7724,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 40.0, "info": { "bracket": "3", "initialLeverage": "40", - "notionalCap": "100000", + "notionalCap": "200000", "notionalFloor": "50000", "maintMarginRatio": "0.01", "cum": "210.0" @@ -7577,113 +7739,113 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 500000.0, + "minNotional": 200000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "4", "initialLeverage": "25", - "notionalCap": "500000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "200000", "maintMarginRatio": "0.02", - "cum": "1210.0" + "cum": "2210.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 500000.0, + "minNotional": 1000000.0, "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "5", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "2000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.05", - "cum": "16210.0" + "notionalFloor": "1000000", + "maintMarginRatio": "0.025", + "cum": "7210.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 2000000.0, - "maxNotional": 6000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "6", - "initialLeverage": "5", - "notionalCap": "6000000", + "initialLeverage": "10", + "notionalCap": "10000000", "notionalFloor": "2000000", - "maintMarginRatio": "0.1", - "cum": "116210.0" + "maintMarginRatio": "0.05", + "cum": "57210.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "7", - "initialLeverage": "4", - "notionalCap": "10000000", - "notionalFloor": "6000000", - "maintMarginRatio": "0.125", - "cum": "266210.0" + "initialLeverage": "5", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.1", + "cum": "557210.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, - "maintenanceMarginRate": 0.15, - "maxLeverage": 3.0, + "minNotional": 20000000.0, + "maxNotional": 25000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "8", - "initialLeverage": "3", - "notionalCap": "20000000", - "notionalFloor": "10000000", - "maintMarginRatio": "0.15", - "cum": "516210.0" + "initialLeverage": "4", + "notionalCap": "25000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.125", + "cum": "1057210.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 20000000.0, - "maxNotional": 30000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "30000000", - "notionalFloor": "20000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.25", - "cum": "2516210.0" + "cum": "4182210.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 30000000.0, - "maxNotional": 50000000.0, + "minNotional": 50000000.0, + "maxNotional": 100000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "30000000", + "notionalCap": "100000000", + "notionalFloor": "50000000", "maintMarginRatio": "0.5", - "cum": "10016210.0" + "cum": "16682210.0" } } ], @@ -8034,128 +8196,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 10000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "50000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" + "initialLeverage": "50", + "notionalCap": "40000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 150000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "275.0" + "initialLeverage": "25", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.02", + "cum": "250.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "1500000", - "notionalFloor": "150000", - "maintMarginRatio": "0.05", - "cum": "4025.0" + "initialLeverage": "20", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.025", + "cum": "1250.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 400000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "3000000", - "notionalFloor": "1500000", - "maintMarginRatio": "0.1", - "cum": "79025.0" + "initialLeverage": "10", + "notionalCap": "2000000", + "notionalFloor": "400000", + "maintMarginRatio": "0.05", + "cum": "11250.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 3750000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "3750000", - "notionalFloor": "3000000", - "maintMarginRatio": "0.125", - "cum": "154025.0" + "initialLeverage": "5", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "111250.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 3750000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 4000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "7500000", - "notionalFloor": "3750000", - "maintMarginRatio": "0.25", - "cum": "622775.0" + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.125", + "cum": "211250.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 15000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "836250.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "15000000", - "notionalFloor": "7500000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.5", - "cum": "2497775.0" + "cum": "3336250.0" } } ], @@ -10702,112 +10880,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "25", - "notionalCap": "50000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 150000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "750.0" + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 50000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", - "maintMarginRatio": "0.05", - "cum": "4500.0" + "initialLeverage": "25", + "notionalCap": "200000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", - "maintMarginRatio": "0.1", - "cum": "17000.0" + "initialLeverage": "20", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.025", + "cum": "1535.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 400000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.125", - "cum": "29500.0" + "initialLeverage": "10", + "notionalCap": "2000000", + "notionalFloor": "400000", + "maintMarginRatio": "0.05", + "cum": "11535.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.25", - "cum": "154500.0" + "initialLeverage": "5", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "111535.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 3000000.0, + "minNotional": 4000000.0, "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.125", + "cum": "211535.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "836535.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "3000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.5", - "cum": "904500.0" + "cum": "3336535.0" } } ], @@ -12136,160 +12346,306 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.005, "maxLeverage": 75.0, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "5000", + "notionalCap": "10000", "notionalFloor": "0", "maintMarginRatio": "0.005", "cum": "0.0" } }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.007, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.007", + "cum": "20.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 40.0, + "info": { + "bracket": "3", + "initialLeverage": "40", + "notionalCap": "750000", + "notionalFloor": "50000", + "maintMarginRatio": "0.01", + "cum": "170.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 750000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "4", + "initialLeverage": "25", + "notionalCap": "800000", + "notionalFloor": "750000", + "maintMarginRatio": "0.02", + "cum": "7670.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 800000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "5", + "initialLeverage": "20", + "notionalCap": "1600000", + "notionalFloor": "800000", + "maintMarginRatio": "0.025", + "cum": "11670.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1600000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "6", + "initialLeverage": "10", + "notionalCap": "8000000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.05", + "cum": "51670.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 8000000.0, + "maxNotional": 16000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "7", + "initialLeverage": "5", + "notionalCap": "16000000", + "notionalFloor": "8000000", + "maintMarginRatio": "0.1", + "cum": "451670.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 16000000.0, + "maxNotional": 20000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "8", + "initialLeverage": "4", + "notionalCap": "20000000", + "notionalFloor": "16000000", + "maintMarginRatio": "0.125", + "cum": "851670.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 20000000.0, + "maxNotional": 40000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "40000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.25", + "cum": "3351670.0" + } + }, + { + "tier": 10.0, + "currency": "USDT", + "minNotional": 40000000.0, + "maxNotional": 80000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "10", + "initialLeverage": "1", + "notionalCap": "80000000", + "notionalFloor": "40000000", + "maintMarginRatio": "0.5", + "cum": "13351670.0" + } + } + ], + "DOGS/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, { "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.006, + "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.006", - "cum": "5.0" + "maintMarginRatio": "0.015", + "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.007, - "maxLeverage": 40.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "40", - "notionalCap": "50000", + "initialLeverage": "25", + "notionalCap": "20000", "notionalFloor": "10000", - "maintMarginRatio": "0.007", - "cum": "15.0" + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "minNotional": 20000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "25", - "notionalCap": "750000", - "notionalFloor": "50000", - "maintMarginRatio": "0.01", - "cum": "165.0" + "initialLeverage": "20", + "notionalCap": "40000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "175.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "20", - "notionalCap": "1100000", - "notionalFloor": "750000", - "maintMarginRatio": "0.025", - "cum": "11415.0" + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.05", + "cum": "1175.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1100000.0, - "maxNotional": 2200000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "10", - "notionalCap": "2200000", - "notionalFloor": "1100000", - "maintMarginRatio": "0.05", - "cum": "38915.0" + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "11175.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2200000.0, - "maxNotional": 5600000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "5", - "notionalCap": "5600000", - "notionalFloor": "2200000", - "maintMarginRatio": "0.1", - "cum": "148915.0" + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "21175.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5600000.0, - "maxNotional": 7000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": "8", - "initialLeverage": "4", - "notionalCap": "7000000", - "notionalFloor": "5600000", - "maintMarginRatio": "0.125", - "cum": "288915.0" + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83675.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 7000000.0, - "maxNotional": 18000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "9", - "initialLeverage": "2", - "notionalCap": "18000000", - "notionalFloor": "7000000", - "maintMarginRatio": "0.25", - "cum": "1163915.0" - } - }, - { - "tier": 10.0, - "currency": "USDT", - "minNotional": 18000000.0, - "maxNotional": 30000000.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "10", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "18000000", + "notionalCap": "2000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "5663915.0" + "cum": "333675.0" } } ], @@ -22772,10 +23128,10 @@ "minNotional": 0.0, "maxNotional": 10000.0, "maintenanceMarginRate": 0.006, - "maxLeverage": 75.0, + "maxLeverage": 51.0, "info": { "bracket": "1", - "initialLeverage": "75", + "initialLeverage": "51", "notionalCap": "10000", "notionalFloor": "0", "maintMarginRatio": "0.006", @@ -22898,13 +23254,13 @@ "tier": 9.0, "currency": "USDC", "minNotional": 18000000.0, - "maxNotional": 30000000.0, + "maxNotional": 19000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "30000000", + "notionalCap": "19000000", "notionalFloor": "18000000", "maintMarginRatio": "0.5", "cum": "5491585.0" @@ -22918,10 +23274,10 @@ "minNotional": 0.0, "maxNotional": 10000.0, "maintenanceMarginRate": 0.006, - "maxLeverage": 75.0, + "maxLeverage": 51.0, "info": { "bracket": "1", - "initialLeverage": "75", + "initialLeverage": "51", "notionalCap": "10000", "notionalFloor": "0", "maintMarginRatio": "0.006", @@ -23044,13 +23400,13 @@ "tier": 9.0, "currency": "USDT", "minNotional": 18000000.0, - "maxNotional": 30000000.0, + "maxNotional": 19000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "30000000", + "notionalCap": "19000000", "notionalFloor": "18000000", "maintMarginRatio": "0.5", "cum": "5491585.0" @@ -23285,6 +23641,152 @@ } } ], + "MBOX/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "40000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "175.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.05", + "cum": "1175.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "11175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "21175.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83675.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333675.0" + } + } + ], "MDT/USDT:USDT": [ { "tier": 1.0, @@ -25226,128 +25728,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 10000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "50000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" + "initialLeverage": "50", + "notionalCap": "60000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 60000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "100000", - "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "275.0" + "initialLeverage": "25", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.02", + "cum": "350.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "100000", - "maintMarginRatio": "0.05", - "cum": "2775.0" + "initialLeverage": "20", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.025", + "cum": "1850.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 600000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.1", - "cum": "52775.0" + "initialLeverage": "10", + "notionalCap": "3000000", + "notionalFloor": "600000", + "maintMarginRatio": "0.05", + "cum": "16850.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 2500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "2500000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.125", - "cum": "102775.0" + "initialLeverage": "5", + "notionalCap": "6000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.1", + "cum": "166850.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 6000000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2500000", - "maintMarginRatio": "0.25", - "cum": "415275.0" + "initialLeverage": "4", + "notionalCap": "7500000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.125", + "cum": "316850.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 7500000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "15000000", + "notionalFloor": "7500000", + "maintMarginRatio": "0.25", + "cum": "1254350.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 15000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "30000000", + "notionalFloor": "15000000", "maintMarginRatio": "0.5", - "cum": "1665275.0" + "cum": "5004350.0" } } ], @@ -25465,6 +25983,152 @@ } } ], + "NULS/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "40000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "175.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.05", + "cum": "1175.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "11175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "21175.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83675.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333675.0" + } + } + ], "OCEAN/USDT:USDT": [ { "tier": 1.0, @@ -26950,13 +27614,13 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.0065, "maxLeverage": 75.0, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "5000", + "notionalCap": "10000", "notionalFloor": "0", "maintMarginRatio": "0.0065", "cum": "0.0" @@ -26965,23 +27629,23 @@ { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 75000.0, + "minNotional": 10000.0, + "maxNotional": 80000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "75000", - "notionalFloor": "5000", + "notionalCap": "80000", + "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "17.5" + "cum": "35.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 75000.0, + "minNotional": 80000.0, "maxNotional": 150000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 40.0, @@ -26989,31 +27653,31 @@ "bracket": "3", "initialLeverage": "40", "notionalCap": "150000", - "notionalFloor": "75000", + "notionalFloor": "80000", "maintMarginRatio": "0.015", - "cum": "392.5" + "cum": "435.0" } }, { "tier": 4.0, "currency": "USDT", "minNotional": 150000.0, - "maxNotional": 300000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "4", "initialLeverage": "25", - "notionalCap": "300000", + "notionalCap": "400000", "notionalFloor": "150000", "maintMarginRatio": "0.02", - "cum": "1142.5" + "cum": "1185.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 300000.0, + "minNotional": 400000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, @@ -27021,47 +27685,47 @@ "bracket": "5", "initialLeverage": "20", "notionalCap": "1000000", - "notionalFloor": "300000", + "notionalFloor": "400000", "maintMarginRatio": "0.025", - "cum": "2642.5" + "cum": "3185.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "6", "initialLeverage": "10", - "notionalCap": "3000000", + "notionalCap": "4000000", "notionalFloor": "1000000", "maintMarginRatio": "0.05", - "cum": "27642.5" + "cum": "28185.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 6000000.0, + "minNotional": 4000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "7", "initialLeverage": "5", - "notionalCap": "6000000", - "notionalFloor": "3000000", + "notionalCap": "8000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.1", - "cum": "177642.5" + "cum": "228185.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 6000000.0, + "minNotional": 8000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, @@ -27069,9 +27733,9 @@ "bracket": "8", "initialLeverage": "4", "notionalCap": "10000000", - "notionalFloor": "6000000", + "notionalFloor": "8000000", "maintMarginRatio": "0.125", - "cum": "327642.5" + "cum": "428185.0" } }, { @@ -27087,23 +27751,23 @@ "notionalCap": "20000000", "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "1577642.5" + "cum": "1678185.0" } }, { "tier": 10.0, "currency": "USDT", "minNotional": 20000000.0, - "maxNotional": 30000000.0, + "maxNotional": 40000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "30000000", + "notionalCap": "40000000", "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "6577642.5" + "cum": "6678185.0" } } ], @@ -27937,6 +28601,152 @@ } } ], + "POPCAT/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "40000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "175.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.05", + "cum": "1175.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "11175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "21175.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83675.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333675.0" + } + } + ], "PORTAL/USDT:USDT": [ { "tier": 1.0, @@ -28626,128 +29436,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 10000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "20000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" + "initialLeverage": "50", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 30000.0, + "maxNotional": 150000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "20000", - "maintMarginRatio": "0.025", - "cum": "125.0" + "initialLeverage": "25", + "notionalCap": "150000", + "notionalFloor": "30000", + "maintMarginRatio": "0.02", + "cum": "200.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 150000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "750.0" + "initialLeverage": "20", + "notionalCap": "300000", + "notionalFloor": "150000", + "maintMarginRatio": "0.025", + "cum": "950.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 300000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "400000", - "notionalFloor": "200000", - "maintMarginRatio": "0.1", - "cum": "10750.0" + "initialLeverage": "10", + "notionalCap": "1500000", + "notionalFloor": "300000", + "maintMarginRatio": "0.05", + "cum": "8450.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "400000", - "maintMarginRatio": "0.125", - "cum": "20750.0" + "initialLeverage": "5", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.1", + "cum": "83450.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 3000000.0, + "maxNotional": 3750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "83250.0" + "initialLeverage": "4", + "notionalCap": "3750000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.125", + "cum": "158450.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 3750000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "7500000", + "notionalFloor": "3750000", + "maintMarginRatio": "0.25", + "cum": "627200.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "15000000", + "notionalFloor": "7500000", "maintMarginRatio": "0.5", - "cum": "333250.0" + "cum": "2502200.0" } } ], @@ -31490,13 +32316,13 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, + "maxNotional": 20000.0, "maintenanceMarginRate": 0.005, "maxLeverage": 100.0, "info": { "bracket": "1", "initialLeverage": "100", - "notionalCap": "10000", + "notionalCap": "20000", "notionalFloor": "0", "maintMarginRatio": "0.005", "cum": "0.0" @@ -31505,161 +32331,145 @@ { "tier": 2.0, "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 50000.0, + "minNotional": 20000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.0065, "maxLeverage": 75.0, "info": { "bracket": "2", "initialLeverage": "75", - "notionalCap": "50000", - "notionalFloor": "10000", + "notionalCap": "100000", + "notionalFloor": "20000", "maintMarginRatio": "0.0065", - "cum": "15.0" + "cum": "30.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 200000.0, + "minNotional": 100000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 50.0, "info": { "bracket": "3", "initialLeverage": "50", - "notionalCap": "200000", - "notionalFloor": "50000", + "notionalCap": "800000", + "notionalFloor": "100000", "maintMarginRatio": "0.01", - "cum": "190.0" + "cum": "380.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.012, - "maxLeverage": 40.0, + "minNotional": 800000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "4", - "initialLeverage": "40", - "notionalCap": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.012", - "cum": "590.0" + "initialLeverage": "25", + "notionalCap": "4000000", + "notionalFloor": "800000", + "maintMarginRatio": "0.02", + "cum": "8380.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 4000000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "5", - "initialLeverage": "25", - "notionalCap": "2000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.02", - "cum": "4590.0" + "initialLeverage": "20", + "notionalCap": "8000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.025", + "cum": "28380.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 2500000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 8000000.0, + "maxNotional": 40000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "6", - "initialLeverage": "20", - "notionalCap": "2500000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.025", - "cum": "14590.0" + "initialLeverage": "10", + "notionalCap": "40000000", + "notionalFloor": "8000000", + "maintMarginRatio": "0.05", + "cum": "228380.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 20000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 40000000.0, + "maxNotional": 80000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "7", - "initialLeverage": "10", - "notionalCap": "20000000", - "notionalFloor": "2500000", - "maintMarginRatio": "0.05", - "cum": "77090.0" + "initialLeverage": "5", + "notionalCap": "80000000", + "notionalFloor": "40000000", + "maintMarginRatio": "0.1", + "cum": "2228380.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 20000000.0, - "maxNotional": 40000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 80000000.0, + "maxNotional": 100000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "8", - "initialLeverage": "5", - "notionalCap": "40000000", - "notionalFloor": "20000000", - "maintMarginRatio": "0.1", - "cum": "1077090.0" + "initialLeverage": "4", + "notionalCap": "100000000", + "notionalFloor": "80000000", + "maintMarginRatio": "0.125", + "cum": "4228380.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 40000000.0, - "maxNotional": 50000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 100000000.0, + "maxNotional": 200000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": "9", - "initialLeverage": "4", - "notionalCap": "50000000", - "notionalFloor": "40000000", - "maintMarginRatio": "0.125", - "cum": "2077090.0" + "initialLeverage": "2", + "notionalCap": "200000000", + "notionalFloor": "100000000", + "maintMarginRatio": "0.25", + "cum": "16728380.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 50000000.0, - "maxNotional": 100000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "10", - "initialLeverage": "2", - "notionalCap": "100000000", - "notionalFloor": "50000000", - "maintMarginRatio": "0.25", - "cum": "8327090.0" - } - }, - { - "tier": 11.0, - "currency": "USDT", - "minNotional": 100000000.0, - "maxNotional": 200000000.0, + "minNotional": 200000000.0, + "maxNotional": 400000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "11", + "bracket": "10", "initialLeverage": "1", - "notionalCap": "200000000", - "notionalFloor": "100000000", + "notionalCap": "400000000", + "notionalFloor": "200000000", "maintMarginRatio": "0.5", - "cum": "33327090.0" + "cum": "66728380.0" } } ], @@ -33063,6 +33873,152 @@ } } ], + "SUN/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "40000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "175.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.05", + "cum": "1175.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "11175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "21175.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83675.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333675.0" + } + } + ], "SUPER/USDT:USDT": [ { "tier": 1.0, @@ -36283,6 +37239,152 @@ } } ], + "VIDT/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "40000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "175.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.05", + "cum": "1175.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "11175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "21175.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83675.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333675.0" + } + } + ], "VOXEL/USDT:USDT": [ { "tier": 1.0, @@ -36906,128 +38008,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 10000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "50000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" + "initialLeverage": "50", + "notionalCap": "100000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "200000", - "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "275.0" + "initialLeverage": "25", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.02", + "cum": "550.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "200000", - "maintMarginRatio": "0.05", - "cum": "5275.0" + "initialLeverage": "20", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.025", + "cum": "3050.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.1", - "cum": "105275.0" + "initialLeverage": "10", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.05", + "cum": "28050.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "4000000", - "maintMarginRatio": "0.125", - "cum": "205275.0" + "initialLeverage": "5", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.1", + "cum": "278050.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 10000000.0, + "maxNotional": 12500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", - "maintMarginRatio": "0.25", - "cum": "830275.0" + "initialLeverage": "4", + "notionalCap": "12500000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.125", + "cum": "528050.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 12500000.0, + "maxNotional": 25000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "25000000", + "notionalFloor": "12500000", + "maintMarginRatio": "0.25", + "cum": "2090550.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.5", - "cum": "3330275.0" + "cum": "8340550.0" } } ], @@ -38092,13 +39210,13 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.005, "maxLeverage": 75.0, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "5000", + "notionalCap": "10000", "notionalFloor": "0", "maintMarginRatio": "0.005", "cum": "0.0" @@ -38107,145 +39225,145 @@ { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.006, + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.0065, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "10000", - "notionalFloor": "5000", - "maintMarginRatio": "0.006", - "cum": "5.0" + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.0065", + "cum": "15.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 50000.0, + "minNotional": 20000.0, + "maxNotional": 160000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 40.0, "info": { "bracket": "3", "initialLeverage": "40", - "notionalCap": "50000", - "notionalFloor": "10000", + "notionalCap": "160000", + "notionalFloor": "20000", "maintMarginRatio": "0.01", - "cum": "45.0" + "cum": "85.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 750000.0, + "minNotional": 160000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "4", "initialLeverage": "25", - "notionalCap": "750000", - "notionalFloor": "50000", + "notionalCap": "800000", + "notionalFloor": "160000", "maintMarginRatio": "0.02", - "cum": "545.0" + "cum": "1685.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 800000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "5", - "initialLeverage": "10", - "notionalCap": "3000000", - "notionalFloor": "750000", - "maintMarginRatio": "0.05", - "cum": "23045.0" + "initialLeverage": "20", + "notionalCap": "1600000", + "notionalFloor": "800000", + "maintMarginRatio": "0.025", + "cum": "5685.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 1600000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "6", - "initialLeverage": "5", - "notionalCap": "10000000", - "notionalFloor": "3000000", - "maintMarginRatio": "0.1", - "cum": "173045.0" + "initialLeverage": "10", + "notionalCap": "8000000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.05", + "cum": "45685.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 12000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 8000000.0, + "maxNotional": 16000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "7", - "initialLeverage": "4", - "notionalCap": "12000000", - "notionalFloor": "10000000", - "maintMarginRatio": "0.125", - "cum": "423045.0" + "initialLeverage": "5", + "notionalCap": "16000000", + "notionalFloor": "8000000", + "maintMarginRatio": "0.1", + "cum": "445685.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 12000000.0, + "minNotional": 16000000.0, "maxNotional": 20000000.0, - "maintenanceMarginRate": 0.15, - "maxLeverage": 3.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "8", - "initialLeverage": "3", + "initialLeverage": "4", "notionalCap": "20000000", - "notionalFloor": "12000000", - "maintMarginRatio": "0.15", - "cum": "723045.0" + "notionalFloor": "16000000", + "maintMarginRatio": "0.125", + "cum": "845685.0" } }, { "tier": 9.0, "currency": "USDT", "minNotional": 20000000.0, - "maxNotional": 30000000.0, + "maxNotional": 40000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "30000000", + "notionalCap": "40000000", "notionalFloor": "20000000", "maintMarginRatio": "0.25", - "cum": "2723045.0" + "cum": "3345685.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 30000000.0, - "maxNotional": 50000000.0, + "minNotional": 40000000.0, + "maxNotional": 80000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "30000000", + "notionalCap": "80000000", + "notionalFloor": "40000000", "maintMarginRatio": "0.5", - "cum": "10223045.0" + "cum": "13345685.0" } } ], From 87678eff982fca1ccafe5b75753d764111e4c823 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 Aug 2024 07:08:41 +0200 Subject: [PATCH 236/242] fix: avoid hyperopt-results not showing past terminal height --- freqtrade/optimize/hyperopt.py | 5 ++-- freqtrade/optimize/hyperopt_output.py | 39 +++++++++++++++++---------- freqtrade/util/rich_progress.py | 10 ++++--- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 574a669f6..428a5cddd 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -17,7 +17,6 @@ import rapidjson from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects from joblib.externals import cloudpickle from pandas import DataFrame -from rich.align import Align from rich.console import Console from freqtrade.constants import DATETIME_PRINT_FORMAT, FTHYPT_FILEVERSION, LAST_BT_RESULT_FN, Config @@ -80,7 +79,7 @@ class Hyperopt: self.max_open_trades_space: List[Dimension] = [] self.dimensions: List[Dimension] = [] - self._hyper_out: HyperoptOutput = HyperoptOutput() + self._hyper_out: HyperoptOutput = HyperoptOutput(streaming=True) self.config = config self.min_date: datetime @@ -635,7 +634,7 @@ class Hyperopt: # Define progressbar with get_progress_tracker( console=console, - cust_objs=[Align.center(self._hyper_out.table)], + cust_callables=[self._hyper_out], ) as pbar: task = pbar.add_task("Epochs", total=self.total_epochs) diff --git a/freqtrade/optimize/hyperopt_output.py b/freqtrade/optimize/hyperopt_output.py index 72e049745..eb6e2c509 100644 --- a/freqtrade/optimize/hyperopt_output.py +++ b/freqtrade/optimize/hyperopt_output.py @@ -1,6 +1,8 @@ import sys -from typing import List, Optional, Union +from os import get_terminal_size +from typing import Any, List, Optional +from rich.align import Align from rich.console import Console from rich.table import Table from rich.text import Text @@ -11,7 +13,16 @@ from freqtrade.util import fmt_coin class HyperoptOutput: - def __init__(self): + def __init__(self, streaming=False) -> None: + self._results: List[Any] = [] + self._streaming = streaming + self.__init_table() + + def __call__(self, *args: Any, **kwds: Any) -> Any: + return Align.center(self.table) + + def __init_table(self) -> None: + """Initialize table""" self.table = Table( title="Hyperopt results", ) @@ -26,17 +37,6 @@ class HyperoptOutput: self.table.add_column("Objective", justify="right") self.table.add_column("Max Drawdown (Acct)", justify="right") - def _add_row(self, data: List[Union[str, Text]]): - """Add single row""" - row_to_add: List[Union[str, Text]] = [r if isinstance(r, Text) else str(r) for r in data] - - self.table.add_row(*row_to_add) - - def _add_rows(self, data: List[List[Union[str, Text]]]): - """add multiple rows""" - for row in data: - self._add_row(row) - def print(self, console: Optional[Console] = None, *, print_colorized=True): if not console: console = Console( @@ -55,8 +55,19 @@ class HyperoptOutput: ) -> None: """Format one or multiple rows and add them""" stake_currency = config["stake_currency"] + self._results.extend(results) - for r in results: + max_rows: Optional[int] = None + + if self._streaming: + ts = get_terminal_size()[1] + # Get terminal size. + # Account for header, borders, and for the progress bar. + # This assumes that lines don't wrap. + max_rows: Optional[int] = -(ts - 6) if self._streaming else None + + self.__init_table() + for r in self._results[max_rows:]: self.table.add_row( *[ # "Best": diff --git a/freqtrade/util/rich_progress.py b/freqtrade/util/rich_progress.py index afa26683e..f4f993f7e 100644 --- a/freqtrade/util/rich_progress.py +++ b/freqtrade/util/rich_progress.py @@ -1,14 +1,18 @@ -from typing import Union +from typing import Callable, List, Union from rich.console import ConsoleRenderable, Group, RichCast from rich.progress import Progress class CustomProgress(Progress): - def __init__(self, *args, cust_objs=[], **kwargs) -> None: + def __init__(self, *args, cust_objs=[], cust_callables: List[Callable] = [], **kwargs) -> None: self._cust_objs = cust_objs + self._cust_callables = cust_callables super().__init__(*args, **kwargs) def get_renderable(self) -> Union[ConsoleRenderable, RichCast, str]: - renderable = Group(*self._cust_objs, *self.get_renderables()) + objs = [obj for obj in self._cust_objs] + for cust_call in self._cust_callables: + objs.append(cust_call()) + renderable = Group(*objs, *self.get_renderables()) return renderable From d05ca3db0b914fe27df0595391b938b8d3abc48f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 Aug 2024 07:14:20 +0200 Subject: [PATCH 237/242] fix: handle small terminal width closes #10572 --- freqtrade/optimize/hyperopt_output.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/hyperopt_output.py b/freqtrade/optimize/hyperopt_output.py index eb6e2c509..74aa8e547 100644 --- a/freqtrade/optimize/hyperopt_output.py +++ b/freqtrade/optimize/hyperopt_output.py @@ -60,11 +60,16 @@ class HyperoptOutput: max_rows: Optional[int] = None if self._streaming: - ts = get_terminal_size()[1] + ts = get_terminal_size() # Get terminal size. # Account for header, borders, and for the progress bar. # This assumes that lines don't wrap. - max_rows: Optional[int] = -(ts - 6) if self._streaming else None + if ts.columns < 148: + # If the terminal is too small, we can't display the table properly. + # We will halve the number of rows to display. + max_rows = -(int(ts.lines / 2) - 6) + else: + max_rows = -(ts.lines - 6) self.__init_table() for r in self._results[max_rows:]: From 59d47955a037788b1482b0bb9116fa04d9c61e1f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 Aug 2024 20:05:48 +0200 Subject: [PATCH 238/242] chore: fix test failure due to terminal error --- freqtrade/optimize/hyperopt_output.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_output.py b/freqtrade/optimize/hyperopt_output.py index 74aa8e547..e3319c255 100644 --- a/freqtrade/optimize/hyperopt_output.py +++ b/freqtrade/optimize/hyperopt_output.py @@ -59,7 +59,7 @@ class HyperoptOutput: max_rows: Optional[int] = None - if self._streaming: + if self._streaming and "pytest" not in sys.modules: ts = get_terminal_size() # Get terminal size. # Account for header, borders, and for the progress bar. From ca3dee7b37a3ceb4c95c523b26a49d625afbfd1e Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 Aug 2024 20:24:52 +0200 Subject: [PATCH 239/242] chore: add setting to avoid deprecation warning from pytest-asyncio --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 5918e92e1..49fdff752 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,6 +86,7 @@ log_format = "%(asctime)s %(levelname)s %(message)s" log_date_format = "%Y-%m-%d %H:%M:%S" asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "function" addopts = "--dist loadscope" [tool.mypy] From 1c5ca0f022cba21c59e82ca1c241f73f0735bcb1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 Aug 2024 20:38:25 +0200 Subject: [PATCH 240/242] chore: improved fix for terminal error --- freqtrade/optimize/hyperopt_output.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/freqtrade/optimize/hyperopt_output.py b/freqtrade/optimize/hyperopt_output.py index e3319c255..c83583d72 100644 --- a/freqtrade/optimize/hyperopt_output.py +++ b/freqtrade/optimize/hyperopt_output.py @@ -59,17 +59,21 @@ class HyperoptOutput: max_rows: Optional[int] = None - if self._streaming and "pytest" not in sys.modules: - ts = get_terminal_size() - # Get terminal size. - # Account for header, borders, and for the progress bar. - # This assumes that lines don't wrap. - if ts.columns < 148: - # If the terminal is too small, we can't display the table properly. - # We will halve the number of rows to display. - max_rows = -(int(ts.lines / 2) - 6) - else: - max_rows = -(ts.lines - 6) + if self._streaming: + try: + ts = get_terminal_size() + # Get terminal size. + # Account for header, borders, and for the progress bar. + # This assumes that lines don't wrap. + if ts.columns < 148: + # If the terminal is too small, we can't display the table properly. + # We will halve the number of rows to display. + max_rows = -(int(ts.lines / 2) - 6) + else: + max_rows = -(ts.lines - 6) + except OSError: + # If we can't get the terminal size, we will just display the last 10 rows. + pass self.__init_table() for r in self._results[max_rows:]: From a250cf7ebe6006319fa2abc25acf23e7a19cf7b1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 Aug 2024 20:38:57 +0200 Subject: [PATCH 241/242] test: Remove unnecessary asyncio decorators --- tests/exchange/test_binance.py | 1 - tests/exchange/test_exchange.py | 5 ----- 2 files changed, 6 deletions(-) diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index 60d0ff556..7b0831520 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -660,7 +660,6 @@ def test__set_leverage_binance(mocker, default_conf): ) -@pytest.mark.asyncio @pytest.mark.parametrize("candle_type", [CandleType.MARK, ""]) async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog, candle_type): ohlcv = [ diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 2e99ddcde..6c9a1a9ba 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2261,7 +2261,6 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_ assert log_has_re(r"Async code raised an exception: .*", caplog) -@pytest.mark.asyncio @pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("candle_type", [CandleType.MARK, CandleType.SPOT]) async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_type): @@ -3235,7 +3234,6 @@ def test_get_rates_testing_exit( @pytest.mark.parametrize("exchange_name", EXCHANGES) -@pytest.mark.asyncio async def test___async_get_candle_history_sort(default_conf, mocker, exchange_name): def sort_data(data, key): return sorted(data, key=key) @@ -3437,7 +3435,6 @@ async def test__async_fetch_trades_contract_size( exchange.close() -@pytest.mark.asyncio @pytest.mark.parametrize("exchange_name", EXCHANGES) async def test__async_get_trade_history_id( default_conf, mocker, exchange_name, fetch_trades_result @@ -3506,7 +3503,6 @@ def test__valid_trade_pagination_id(mocker, default_conf_usdt, exchange_name, tr assert exchange._valid_trade_pagination_id("XRP/USDT", trade_id) == expected -@pytest.mark.asyncio @pytest.mark.parametrize("exchange_name", EXCHANGES) async def test__async_get_trade_history_time( default_conf, mocker, caplog, exchange_name, fetch_trades_result @@ -3548,7 +3544,6 @@ async def test__async_get_trade_history_time( assert log_has_re(r"Stopping because until was reached.*", caplog) -@pytest.mark.asyncio @pytest.mark.parametrize("exchange_name", EXCHANGES) async def test__async_get_trade_history_time_empty( default_conf, mocker, caplog, exchange_name, trades_history From 5e9d2323e396a40e9825d400701f14f35a373742 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 31 Aug 2024 08:25:52 +0200 Subject: [PATCH 242/242] chore: bump version to 2024.8 --- freqtrade/__init__.py | 2 +- ft_client/freqtrade_client/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index be9980671..b1cd1c9cb 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,6 +1,6 @@ """Freqtrade bot""" -__version__ = "2024.8-dev" +__version__ = "2024.8" if "dev" in __version__: from pathlib import Path diff --git a/ft_client/freqtrade_client/__init__.py b/ft_client/freqtrade_client/__init__.py index 68ef44422..ea75c43e1 100644 --- a/ft_client/freqtrade_client/__init__.py +++ b/ft_client/freqtrade_client/__init__.py @@ -1,7 +1,7 @@ from freqtrade_client.ft_rest_client import FtRestClient -__version__ = "2024.8-dev" +__version__ = "2024.8" if "dev" in __version__: from pathlib import Path