From a9195c8ff93f392d3be61793fa59f93f812530c5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 16:49:07 +0100 Subject: [PATCH 01/23] chore: remove pointless else --- freqtrade/commands/list_commands.py | 153 ++++++++++++++-------------- 1 file changed, 76 insertions(+), 77 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index c8d476717..b33421858 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -246,88 +246,87 @@ def start_list_markets(args: dict[str, Any], pairs_only: bool = False) -> None: except Exception as e: raise OperationalException(f"Cannot get markets. Reason: {e}") from e - else: - summary_str = ( - (f"Exchange {exchange.name} has {len(pairs)} ") - + ("active " if active_only else "") - + (plural(len(pairs), "pair" if pairs_only else "market")) - + ( - f" with {', '.join(base_currencies)} as base " - f"{plural(len(base_currencies), 'currency', 'currencies')}" - if base_currencies - else "" - ) - + (" and" if base_currencies and quote_currencies else "") - + ( - f" with {', '.join(quote_currencies)} as quote " - f"{plural(len(quote_currencies), 'currency', 'currencies')}" - if quote_currencies - else "" - ) + summary_str = ( + (f"Exchange {exchange.name} has {len(pairs)} ") + + ("active " if active_only else "") + + (plural(len(pairs), "pair" if pairs_only else "market")) + + ( + f" with {', '.join(base_currencies)} as base " + f"{plural(len(base_currencies), 'currency', 'currencies')}" + if base_currencies + else "" ) + + (" and" if base_currencies and quote_currencies else "") + + ( + f" with {', '.join(quote_currencies)} as quote " + f"{plural(len(quote_currencies), 'currency', 'currencies')}" + if quote_currencies + else "" + ) + ) - headers = [ - "Id", - "Symbol", - "Base", - "Quote", - "Active", - "Spot", - "Margin", - "Future", - "Leverage", - ] + headers = [ + "Id", + "Symbol", + "Base", + "Quote", + "Active", + "Spot", + "Margin", + "Future", + "Leverage", + ] - tabular_data = [ - { - "Id": v["id"], - "Symbol": v["symbol"], - "Base": v["base"], - "Quote": v["quote"], - "Active": market_is_active(v), - "Spot": "Spot" if exchange.market_is_spot(v) else "", - "Margin": "Margin" if exchange.market_is_margin(v) else "", - "Future": "Future" if exchange.market_is_future(v) else "", - "Leverage": exchange.get_max_leverage(v["symbol"], 20), - } - for _, v in pairs.items() - ] + tabular_data = [ + { + "Id": v["id"], + "Symbol": v["symbol"], + "Base": v["base"], + "Quote": v["quote"], + "Active": market_is_active(v), + "Spot": "Spot" if exchange.market_is_spot(v) else "", + "Margin": "Margin" if exchange.market_is_margin(v) else "", + "Future": "Future" if exchange.market_is_future(v) else "", + "Leverage": exchange.get_max_leverage(v["symbol"], 20), + } + for _, v in pairs.items() + ] - if ( - args.get("print_one_column", False) - or args.get("list_pairs_print_json", False) - or args.get("print_csv", False) - ): - # Print summary string in the log in case of machine-readable - # regular formats. - logger.info(f"{summary_str}.") + if ( + args.get("print_one_column", False) + or args.get("list_pairs_print_json", False) + or args.get("print_csv", False) + ): + # Print summary string in the log in case of machine-readable + # regular formats. + logger.info(f"{summary_str}.") + else: + # Print empty string separating leading logs and output in case of + # human-readable formats. + print() + + if pairs: + if args.get("print_list", False): + # print data as a list, with human-readable summary + print(f"{summary_str}: {', '.join(pairs.keys())}.") + elif args.get("print_one_column", False): + print("\n".join(pairs.keys())) + elif args.get("list_pairs_print_json", False): + import rapidjson + + print(rapidjson.dumps(list(pairs.keys()), default=str)) + elif args.get("print_csv", False): + writer = csv.DictWriter(sys.stdout, fieldnames=headers) + writer.writeheader() + writer.writerows(tabular_data) else: - # Print empty string separating leading logs and output in case of - # human-readable formats. - print() - - if pairs: - if args.get("print_list", False): - # print data as a list, with human-readable summary - print(f"{summary_str}: {', '.join(pairs.keys())}.") - elif args.get("print_one_column", False): - print("\n".join(pairs.keys())) - elif args.get("list_pairs_print_json", False): - import rapidjson - - print(rapidjson.dumps(list(pairs.keys()), default=str)) - elif args.get("print_csv", False): - writer = csv.DictWriter(sys.stdout, fieldnames=headers) - writer.writeheader() - writer.writerows(tabular_data) - else: - print_rich_table(tabular_data, headers, summary_str) - elif not ( - args.get("print_one_column", False) - or args.get("list_pairs_print_json", False) - or args.get("print_csv", False) - ): - print(f"{summary_str}.") + print_rich_table(tabular_data, headers, summary_str) + elif not ( + args.get("print_one_column", False) + or args.get("list_pairs_print_json", False) + or args.get("print_csv", False) + ): + print(f"{summary_str}.") def start_show_trades(args: dict[str, Any]) -> None: From 8bd4926c29259aec048df7fd444c3dd535292084 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 17:00:55 +0100 Subject: [PATCH 02/23] feat: add Min Stake to list-pairs command --- freqtrade/commands/list_commands.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index b33421858..e3e71911b 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -6,6 +6,7 @@ from typing import Any from freqtrade.enums import RunMode from freqtrade.exceptions import ConfigurationError, OperationalException from freqtrade.ft_types import ValidExchangesType +from freqtrade.misc import safe_value_fallback logger = logging.getLogger(__name__) @@ -246,6 +247,8 @@ def start_list_markets(args: dict[str, Any], pairs_only: bool = False) -> None: except Exception as e: raise OperationalException(f"Cannot get markets. Reason: {e}") from e + tickers = exchange.get_tickers() + summary_str = ( (f"Exchange {exchange.name} has {len(pairs)} ") + ("active " if active_only else "") @@ -275,6 +278,7 @@ def start_list_markets(args: dict[str, Any], pairs_only: bool = False) -> None: "Margin", "Future", "Leverage", + "Min Stake", ] tabular_data = [ @@ -288,6 +292,14 @@ def start_list_markets(args: dict[str, Any], pairs_only: bool = False) -> None: "Margin": "Margin" if exchange.market_is_margin(v) else "", "Future": "Future" if exchange.market_is_future(v) else "", "Leverage": exchange.get_max_leverage(v["symbol"], 20), + "Min Stake": round( + exchange.get_min_pair_stake_amount( + v["symbol"], + safe_value_fallback(tickers.get(v["symbol"], {}), "last", "ask", 0.0), + 0.0, + ), + 8, + ), } for _, v in pairs.items() ] From 538139ef31cf8b8567a396561532036b70af0a73 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 17:07:46 +0100 Subject: [PATCH 03/23] chore: fix incorrect docstrings --- freqtrade/exchange/binance.py | 6 ++---- freqtrade/exchange/bybit.py | 2 -- freqtrade/exchange/exchange.py | 6 ++---- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 18ba2aa50..c67263096 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -274,12 +274,12 @@ class Binance(Exchange): def dry_run_liquidation_price( self, pair: str, - open_rate: float, # Entry price of position + open_rate: float, is_short: bool, amount: float, stake_amount: float, leverage: float, - wallet_balance: float, # Or margin balance + wallet_balance: float, open_trades: list, ) -> float | None: """ @@ -293,8 +293,6 @@ class Binance(Exchange): :param amount: Absolute value of position size incl. leverage (in base currency) :param stake_amount: Stake amount - Collateral in settle currency. :param leverage: Leverage used for this position. - :param trading_mode: SPOT, MARGIN, FUTURES, etc. - :param margin_mode: Either ISOLATED or CROSS :param wallet_balance: Amount of margin_mode in the wallet being used to trade Cross-Margin Mode: crossWalletBalance Isolated-Margin Mode: isolatedWalletBalance diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index d75aafdc8..ae15ad521 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -184,8 +184,6 @@ class Bybit(Exchange): :param amount: Absolute value of position size incl. leverage (in base currency) :param stake_amount: Stake amount - Collateral in settle currency. :param leverage: Leverage used for this position. - :param trading_mode: SPOT, MARGIN, FUTURES, etc. - :param margin_mode: Either ISOLATED or CROSS :param wallet_balance: Amount of margin_mode in the wallet being used to trade Cross-Margin Mode: crossWalletBalance Isolated-Margin Mode: isolatedWalletBalance diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index d234c3568..14b801bef 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -3688,12 +3688,12 @@ class Exchange: def dry_run_liquidation_price( self, pair: str, - open_rate: float, # Entry price of position + open_rate: float, is_short: bool, amount: float, stake_amount: float, leverage: float, - wallet_balance: float, # Or margin balance + wallet_balance: float, open_trades: list, ) -> float | None: """ @@ -3714,8 +3714,6 @@ class Exchange: :param amount: Absolute value of position size incl. leverage (in base currency) :param stake_amount: Stake amount - Collateral in settle currency. :param leverage: Leverage used for this position. - :param trading_mode: SPOT, MARGIN, FUTURES, etc. - :param margin_mode: Either ISOLATED or CROSS :param wallet_balance: Amount of margin_mode in the wallet being used to trade Cross-Margin Mode: crossWalletBalance Isolated-Margin Mode: isolatedWalletBalance From cd971cff4f13ed5dd085a95cb843c53f73a64bd0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 17:27:25 +0100 Subject: [PATCH 04/23] test: improve liquidation-calculation test Move maintenance margin to parameter, add tests from bybit page --- tests/exchange/test_exchange.py | 68 +++++++++++++++++---------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 138884698..f32f177ea 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -6077,44 +6077,47 @@ def test_get_liquidation_price1(mocker, default_conf): @pytest.mark.parametrize("liquidation_buffer", [0.0]) @pytest.mark.parametrize( - "is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,expected_liq", + "is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,mramt,expected_liq", [ - (False, "spot", "binance", "", 5.0, 10.0, 1.0, None), - (True, "spot", "binance", "", 5.0, 10.0, 1.0, None), - (False, "spot", "gate", "", 5.0, 10.0, 1.0, None), - (True, "spot", "gate", "", 5.0, 10.0, 1.0, None), - (False, "spot", "okx", "", 5.0, 10.0, 1.0, None), - (True, "spot", "okx", "", 5.0, 10.0, 1.0, None), + (False, "spot", "binance", "", 5.0, 10.0, 1.0, (0.01, 0.01), None), + (True, "spot", "binance", "", 5.0, 10.0, 1.0, (0.01, 0.01), None), + (False, "spot", "gate", "", 5.0, 10.0, 1.0, (0.01, 0.01), None), + (True, "spot", "gate", "", 5.0, 10.0, 1.0, (0.01, 0.01), None), + (False, "spot", "okx", "", 5.0, 10.0, 1.0, (0.01, 0.01), None), + (True, "spot", "okx", "", 5.0, 10.0, 1.0, (0.01, 0.01), None), # Binance, short - (True, "futures", "binance", "isolated", 5.0, 10.0, 1.0, 11.89108910891089), - (True, "futures", "binance", "isolated", 3.0, 10.0, 1.0, 13.211221122079207), - (True, "futures", "binance", "isolated", 5.0, 8.0, 1.0, 9.514851485148514), - (True, "futures", "binance", "isolated", 5.0, 10.0, 0.6, 11.897689768976898), + (True, "futures", "binance", "isolated", 5.0, 10.0, 1.0, (0.01, 0.01), 11.89108910891089), + (True, "futures", "binance", "isolated", 3.0, 10.0, 1.0, (0.01, 0.01), 13.211221122079207), + (True, "futures", "binance", "isolated", 5.0, 8.0, 1.0, (0.01, 0.01), 9.514851485148514), + (True, "futures", "binance", "isolated", 5.0, 10.0, 0.6, (0.01, 0.01), 11.897689768976898), # Binance, long - (False, "futures", "binance", "isolated", 5, 10, 1.0, 8.070707070707071), - (False, "futures", "binance", "isolated", 5, 8, 1.0, 6.454545454545454), - (False, "futures", "binance", "isolated", 3, 10, 1.0, 6.723905723905723), - (False, "futures", "binance", "isolated", 5, 10, 0.6, 8.063973063973064), + (False, "futures", "binance", "isolated", 5, 10, 1.0, (0.01, 0.01), 8.070707070707071), + (False, "futures", "binance", "isolated", 5, 8, 1.0, (0.01, 0.01), 6.454545454545454), + (False, "futures", "binance", "isolated", 3, 10, 1.0, (0.01, 0.01), 6.723905723905723), + (False, "futures", "binance", "isolated", 5, 10, 0.6, (0.01, 0.01), 8.063973063973064), # Gate/okx, short - (True, "futures", "gate", "isolated", 5, 10, 1.0, 11.87413417771621), - (True, "futures", "gate", "isolated", 5, 10, 2.0, 11.87413417771621), - (True, "futures", "gate", "isolated", 3, 10, 1.0, 13.193482419684678), - (True, "futures", "gate", "isolated", 5, 8, 1.0, 9.499307342172967), - (True, "futures", "okx", "isolated", 3, 10, 1.0, 13.193482419684678), + (True, "futures", "gate", "isolated", 5, 10, 1.0, (0.01, 0.01), 11.87413417771621), + (True, "futures", "gate", "isolated", 5, 10, 2.0, (0.01, 0.01), 11.87413417771621), + (True, "futures", "gate", "isolated", 3, 10, 1.0, (0.01, 0.01), 13.193482419684678), + (True, "futures", "gate", "isolated", 5, 8, 1.0, (0.01, 0.01), 9.499307342172967), + (True, "futures", "okx", "isolated", 3, 10, 1.0, (0.01, 0.01), 13.193482419684678), # Gate/okx, long - (False, "futures", "gate", "isolated", 5.0, 10.0, 1.0, 8.085708510208207), - (False, "futures", "gate", "isolated", 3.0, 10.0, 1.0, 6.738090425173506), - (False, "futures", "okx", "isolated", 3.0, 10.0, 1.0, 6.738090425173506), + (False, "futures", "gate", "isolated", 5.0, 10.0, 1.0, (0.01, 0.01), 8.085708510208207), + (False, "futures", "gate", "isolated", 3.0, 10.0, 1.0, (0.01, 0.01), 6.738090425173506), + (False, "futures", "okx", "isolated", 3.0, 10.0, 1.0, (0.01, 0.01), 6.738090425173506), # bybit, long - (False, "futures", "bybit", "isolated", 1.0, 10.0, 1.0, 0.1), - (False, "futures", "bybit", "isolated", 3.0, 10.0, 1.0, 6.7666666), - (False, "futures", "bybit", "isolated", 5.0, 10.0, 1.0, 8.1), - (False, "futures", "bybit", "isolated", 10.0, 10.0, 1.0, 9.1), + (False, "futures", "bybit", "isolated", 1.0, 10.0, 1.0, (0.01, 0.01), 0.1), + (False, "futures", "bybit", "isolated", 3.0, 10.0, 1.0, (0.01, 0.01), 6.7666666), + (False, "futures", "bybit", "isolated", 5.0, 10.0, 1.0, (0.01, 0.01), 8.1), + (False, "futures", "bybit", "isolated", 10.0, 10.0, 1.0, (0.01, 0.01), 9.1), + # From the bybit example - without additional margin + (False, "futures", "bybit", "isolated", 50.0, 40000.0, 1.0, (0.005, None), 39400), + (False, "futures", "bybit", "isolated", 50.0, 20000.0, 1.0, (0.005, None), 19700), # bybit, short - (True, "futures", "bybit", "isolated", 1.0, 10.0, 1.0, 19.9), - (True, "futures", "bybit", "isolated", 3.0, 10.0, 1.0, 13.233333), - (True, "futures", "bybit", "isolated", 5.0, 10.0, 1.0, 11.9), - (True, "futures", "bybit", "isolated", 10.0, 10.0, 1.0, 10.9), + (True, "futures", "bybit", "isolated", 1.0, 10.0, 1.0, (0.01, 0.01), 19.9), + (True, "futures", "bybit", "isolated", 3.0, 10.0, 1.0, (0.01, 0.01), 13.233333), + (True, "futures", "bybit", "isolated", 5.0, 10.0, 1.0, (0.01, 0.01), 11.9), + (True, "futures", "bybit", "isolated", 10.0, 10.0, 1.0, (0.01, 0.01), 10.9), ], ) def test_get_liquidation_price( @@ -6127,6 +6130,7 @@ def test_get_liquidation_price( leverage, open_rate, amount, + mramt, expected_liq, liquidation_buffer, ): @@ -6190,7 +6194,7 @@ def test_get_liquidation_price( mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y, **kwargs: y) exchange = get_patched_exchange(mocker, default_conf_usdt, exchange=exchange_name) - exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01)) + exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=mramt) exchange.name = exchange_name # default_conf_usdt.update({ # "dry_run": False, From 4a7140c05df522e4d234e4c2e2f32e6de1108e05 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 17:29:55 +0100 Subject: [PATCH 05/23] chore: Update bybit dry-liquidation calculation the result remains the same - but the calculation now matches the bybit documentation better. --- freqtrade/exchange/bybit.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index ae15ad521..1f5c19b2a 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -166,15 +166,16 @@ class Bybit(Exchange): PERPETUAL: bybit: https://www.bybithelp.com/HelpCenterKnowledge/bybitHC_Article?language=en_US&id=000001067 + https://www.bybit.com/en/help-center/article/Liquidation-Price-Calculation-under-Isolated-Mode-Unified-Trading-Account#b Long: Liquidation Price = ( - Entry Price * (1 - Initial Margin Rate + Maintenance Margin Rate) - - Extra Margin Added/ Contract) + Entry Price - [(Initial Margin - Maintenance Margin)/Contract Quantity] + - (Extra Margin Added/Contract Quantity)) Short: Liquidation Price = ( - Entry Price * (1 + Initial Margin Rate - Maintenance Margin Rate) - + Extra Margin Added/ Contract) + Entry Price + [(Initial Margin - Maintenance Margin)/Contract Quantity] + + (Extra Margin Added/Contract Quantity)) Implementation Note: Extra margin is currently not used. @@ -196,13 +197,16 @@ class Bybit(Exchange): if self.trading_mode == TradingMode.FUTURES and self.margin_mode == MarginMode.ISOLATED: if market["inverse"]: raise OperationalException("Freqtrade does not yet support inverse contracts") - initial_margin_rate = 1 / leverage + position_value = amount * open_rate + initial_margin = position_value / leverage + maintenance_margin = position_value * mm_ratio + margin_diff_per_contract = (initial_margin - maintenance_margin) / amount # See docstring - ignores extra margin! if is_short: - return open_rate * (1 + initial_margin_rate - mm_ratio) + return open_rate + margin_diff_per_contract else: - return open_rate * (1 - initial_margin_rate + mm_ratio) + return open_rate - margin_diff_per_contract else: raise OperationalException( From ac29ef67fdbb72d8b07b2da85d5683b45df179aa Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 19:39:02 +0100 Subject: [PATCH 06/23] chore: fall back to 0.0 if min-pair returns None --- freqtrade/commands/list_commands.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index e3e71911b..ae51192a3 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -297,7 +297,8 @@ def start_list_markets(args: dict[str, Any], pairs_only: bool = False) -> None: v["symbol"], safe_value_fallback(tickers.get(v["symbol"], {}), "last", "ask", 0.0), 0.0, - ), + ) + or 0.0, 8, ), } From 6efe6bbfc2dc0ca0e32900650bc8893652b3faea Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 19:53:11 +0100 Subject: [PATCH 07/23] fix: restore previous startup time --- freqtrade/commands/list_commands.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index ae51192a3..0784e5134 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -6,7 +6,6 @@ from typing import Any from freqtrade.enums import RunMode from freqtrade.exceptions import ConfigurationError, OperationalException from freqtrade.ft_types import ValidExchangesType -from freqtrade.misc import safe_value_fallback logger = logging.getLogger(__name__) @@ -220,7 +219,7 @@ def start_list_markets(args: dict[str, Any], pairs_only: bool = False) -> None: """ from freqtrade.configuration import setup_utils_configuration from freqtrade.exchange import market_is_active - from freqtrade.misc import plural + from freqtrade.misc import plural, safe_value_fallback from freqtrade.resolvers import ExchangeResolver from freqtrade.util import print_rich_table From 35cc7da9b308fa3517f12571ed14d0973b0095a3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Mar 2025 15:00:13 +0100 Subject: [PATCH 08/23] fix: keep pair index per pair closes #11479 --- freqtrade/data/dataprovider.py | 13 +++++++------ freqtrade/optimize/backtesting.py | 4 +++- tests/data/test_dataprovider.py | 8 ++++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index b979faff6..1f2bcd82d 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -49,7 +49,7 @@ class DataProvider: self._pairlists = pairlists self.__rpc = rpc self.__cached_pairs: dict[PairWithTimeframe, tuple[DataFrame, datetime]] = {} - self.__slice_index: int | None = None + self.__slice_index: dict[str, int] = {} self.__slice_date: datetime | None = None self.__cached_pairs_backtesting: dict[PairWithTimeframe, DataFrame] = {} @@ -69,13 +69,13 @@ class DataProvider: self.producers = self._config.get("external_message_consumer", {}).get("producers", []) self.external_data_enabled = len(self.producers) > 0 - def _set_dataframe_max_index(self, limit_index: int): + def _set_dataframe_max_index(self, pair: str, limit_index: int): """ Limit analyzed dataframe to max specified index. Only relevant in backtesting. :param limit_index: dataframe index. """ - self.__slice_index = limit_index + self.__slice_index[pair] = limit_index def _set_dataframe_max_date(self, limit_date: datetime): """ @@ -393,9 +393,10 @@ class DataProvider: df, date = self.__cached_pairs[pair_key] else: df, date = self.__cached_pairs[pair_key] - if self.__slice_index is not None: - max_index = self.__slice_index + if (max_index := self.__slice_index.get(pair)) is not None: df = df.iloc[max(0, max_index - MAX_DATAFRAME_CANDLES) : max_index] + else: + return (DataFrame(), datetime.fromtimestamp(0, tz=timezone.utc)) return df, date else: return (DataFrame(), datetime.fromtimestamp(0, tz=timezone.utc)) @@ -430,7 +431,7 @@ class DataProvider: # Don't reset backtesting pairs - # otherwise they're reloaded each time during hyperopt due to with analyze_per_epoch # self.__cached_pairs_backtesting = {} - self.__slice_index = 0 + self.__slice_index = {} # Exchange functions diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 56541f2fe..acce51537 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1552,7 +1552,9 @@ class Backtesting: row_index += 1 indexes[pair] = row_index is_last_row = current_time == end_date - self.dataprovider._set_dataframe_max_index(self.required_startup + row_index) + self.dataprovider._set_dataframe_max_index( + pair, self.required_startup + row_index + ) trade_dir = self.check_for_trade_entry(row) pair_tradedir_cache[pair] = trade_dir diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index bef894eff..c2adb84bb 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -408,20 +408,20 @@ def test_get_analyzed_dataframe(mocker, default_conf, ohlcv_history): # Test backtest mode default_conf["runmode"] = RunMode.BACKTEST - dp._set_dataframe_max_index(1) + dp._set_dataframe_max_index("XRP/BTC", 1) dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe) assert len(dataframe) == 1 - dp._set_dataframe_max_index(2) + dp._set_dataframe_max_index("XRP/BTC", 2) dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe) assert len(dataframe) == 2 - dp._set_dataframe_max_index(3) + dp._set_dataframe_max_index("XRP/BTC", 3) dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe) assert len(dataframe) == 3 - dp._set_dataframe_max_index(500) + dp._set_dataframe_max_index("XRP/BTC", 500) dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe) assert len(dataframe) == len(ohlcv_history) From 805e03b83b971c957ed73136494a96d6a0b8a5ac Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Mar 2025 15:00:20 +0100 Subject: [PATCH 09/23] test: update test for new bt cache behavior --- tests/optimize/test_backtesting.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index f14e087af..d5bc7d5b4 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1543,8 +1543,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) 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 + removed_candles = len(data[pair]) - 1 assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, "5m")[0]) == removed_candles assert ( len(backtesting.dataprovider.get_analyzed_dataframe("NXT/BTC", "5m")[0]) @@ -1663,8 +1662,7 @@ def test_backtest_multi_pair_detail( 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 + removed_candles = len(data[pair]) - 1 assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, "5m")[0]) == removed_candles assert ( len(backtesting.dataprovider.get_analyzed_dataframe("NXT/USDT", "5m")[0]) @@ -1793,7 +1791,7 @@ def test_backtest_multi_pair_detail_simplified( assert len(evaluate_result_multi(results["results"], "1m", 3)) == 0 # # Cached data correctly removed amounts - offset = 1 if tres == 0 else 0 + offset = 1 removed_candles = len(data[pair]) - offset assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, "1h")[0]) == removed_candles assert ( From a0cbdc91355cb2703f0eaf5dca0085abe56b3d77 Mon Sep 17 00:00:00 2001 From: mrpabloyeah Date: Sun, 9 Mar 2025 18:21:33 +0100 Subject: [PATCH 10/23] Add year to backtest breakdowns --- freqtrade/constants.py | 2 +- freqtrade/optimize/optimize_reports/optimize_reports.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 2d3b4a5e8..1b99dd6ec 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -59,7 +59,7 @@ AVAILABLE_PAIRLISTS = [ "VolatilityFilter", ] AVAILABLE_DATAHANDLERS = ["json", "jsongz", "feather", "parquet"] -BACKTEST_BREAKDOWNS = ["day", "week", "month"] +BACKTEST_BREAKDOWNS = ["day", "week", "month", "year"] BACKTEST_CACHE_AGE = ["none", "day", "week", "month"] BACKTEST_CACHE_DEFAULT = "day" DRY_RUN_WALLET = 1000 diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index edcdf757a..a798eaf7a 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -212,6 +212,8 @@ def _get_resample_from_period(period: str) -> str: return "1W-MON" if period == "month": return "1ME" + if period == "year": + return "1Y" raise ValueError(f"Period {period} is not supported.") From 98b6f58eb2cadbe067a9a87066dd62b6b0510ff7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 03:52:54 +0000 Subject: [PATCH 11/23] chore(deps-dev): bump types-requests in the types group Bumps the types group with 1 update: [types-requests](https://github.com/python/typeshed). Updates `types-requests` from 2.32.0.20250301 to 2.32.0.20250306 - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types ... 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 5d0e55f4e..9ab747b71 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -27,6 +27,6 @@ nbconvert==7.16.6 # mypy types types-cachetools==5.5.0.20240820 types-filelock==3.2.7 -types-requests==2.32.0.20250301 +types-requests==2.32.0.20250306 types-tabulate==0.9.0.20241207 types-python-dateutil==2.9.0.20241206 From 30d2298d54953c323df25defb29245069e74c2f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 03:53:40 +0000 Subject: [PATCH 12/23] chore(deps): bump websockets from 15.0 to 15.0.1 Bumps [websockets](https://github.com/python-websockets/websockets) from 15.0 to 15.0.1. - [Release notes](https://github.com/python-websockets/websockets/releases) - [Commits](https://github.com/python-websockets/websockets/compare/15.0...15.0.1) --- updated-dependencies: - dependency-name: websockets 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 16787249d..daf3b7ebe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -55,7 +55,7 @@ pytz==2025.1 schedule==1.2.2 #WS Messages -websockets==15.0 +websockets==15.0.1 janus==2.0.0 ast-comments==1.2.2 From 5b1011668ac9022f47f16b58ebe52a577673e142 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 03:53:55 +0000 Subject: [PATCH 13/23] chore(deps-dev): bump ruff from 0.9.9 to 0.9.10 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.9 to 0.9.10. - [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.9.9...0.9.10) --- 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 5d0e55f4e..b5bf9dd4a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==4.0.1 -ruff==0.9.9 +ruff==0.9.10 mypy==1.15.0 pre-commit==4.1.0 pytest==8.3.5 From 6d84b93715d1c4d530c924ce06506825a6bfc61f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 03:54:03 +0000 Subject: [PATCH 14/23] chore(deps): bump ccxt from 4.4.64 to 4.4.65 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.64 to 4.4.65. - [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/v4.4.64...v4.4.65) --- 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 16787249d..e93a9fc98 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ bottleneck==1.4.2 numexpr==2.10.2 pandas-ta==0.3.14b -ccxt==4.4.64 +ccxt==4.4.65 cryptography==44.0.2 aiohttp==3.9.5 SQLAlchemy==2.0.38 From 6613366343d80f3970a70cfcc6b39c096debac0a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 10 Mar 2025 06:28:39 +0100 Subject: [PATCH 15/23] chore: bump types-requests for pre-commit config --- .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 6f423d3ec..bb2d50511 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: additional_dependencies: - types-cachetools==5.5.0.20240820 - types-filelock==3.2.7 - - types-requests==2.32.0.20250301 + - types-requests==2.32.0.20250306 - types-tabulate==0.9.0.20241207 - types-python-dateutil==2.9.0.20241206 - SQLAlchemy==2.0.38 From 3281049264d2d33b01b852cc9835c0a6292854fe Mon Sep 17 00:00:00 2001 From: mrpabloyeah Date: Mon, 10 Mar 2025 18:44:01 +0100 Subject: [PATCH 16/23] Add year also in cli_options --- freqtrade/commands/cli_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 16f5d1c4d..6eaa9e3a5 100755 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -224,7 +224,7 @@ AVAILABLE_CLI_OPTIONS = { ), "backtest_breakdown": Arg( "--breakdown", - help="Show backtesting breakdown per [day, week, month].", + help="Show backtesting breakdown per [day, week, month, year].", nargs="+", choices=constants.BACKTEST_BREAKDOWNS, ), From b0b9e398e1503fb9785431f58f0bffad6544c176 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 10 Mar 2025 21:13:32 +0100 Subject: [PATCH 17/23] fix: assume 200char terminal if no terminal size is available closes #11477 --- freqtrade/commands/list_commands.py | 12 +++++------- freqtrade/loggers/__init__.py | 6 +++--- freqtrade/loggers/rich_console.py | 26 ++++++++++++++++++++++++++ freqtrade/util/progress_tracker.py | 3 ++- freqtrade/util/rich_tables.py | 16 ++++------------ 5 files changed, 40 insertions(+), 23 deletions(-) create mode 100644 freqtrade/loggers/rich_console.py diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 0784e5134..846b97693 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -17,11 +17,11 @@ def start_list_exchanges(args: dict[str, Any]) -> None: :param args: Cli args from Arguments() :return: None """ - from rich.console import Console from rich.table import Table from rich.text import Text from freqtrade.exchange import list_available_exchanges + from freqtrade.loggers.rich_console import get_rich_console available_exchanges: list[ValidExchangesType] = list_available_exchanges( args["list_exchanges_all"] @@ -77,15 +77,16 @@ def start_list_exchanges(args: dict[str, Any]) -> None: ) # table.add_row(*[exchange[header] for header in headers]) - console = Console() + console = get_rich_console() console.print(table) def _print_objs_tabular(objs: list, print_colorized: bool) -> None: - from rich.console import Console from rich.table import Table from rich.text import Text + from freqtrade.loggers.rich_console import get_rich_console + names = [s["name"] for s in objs] objs_to_print: list[dict[str, Text | str]] = [ { @@ -118,10 +119,7 @@ def _print_objs_tabular(objs: list, print_colorized: bool) -> None: for row in objs_to_print: table.add_row(*[row[header] for header in objs_to_print[0].keys()]) - console = Console( - color_system="auto" if print_colorized else None, - width=200 if "pytest" in sys.modules else None, - ) + console = get_rich_console(color_system="auto" if print_colorized else None) console.print(table) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index 69e372a29..1d01d4177 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -3,12 +3,11 @@ from logging import Formatter from logging.handlers import RotatingFileHandler, SysLogHandler from pathlib import Path -from rich.console import Console - from freqtrade.constants import Config from freqtrade.exceptions import OperationalException from freqtrade.loggers.buffering_handler import FTBufferingHandler from freqtrade.loggers.ft_rich_handler import FtRichHandler +from freqtrade.loggers.rich_console import get_rich_console from freqtrade.loggers.set_log_levels import set_loggers @@ -22,7 +21,8 @@ LOGFORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" bufferHandler = FTBufferingHandler(1000) bufferHandler.setFormatter(Formatter(LOGFORMAT)) -error_console = Console(stderr=True, color_system=None) + +error_console = get_rich_console(stderr=True, color_system=None) def get_existing_handlers(handlertype): diff --git a/freqtrade/loggers/rich_console.py b/freqtrade/loggers/rich_console.py new file mode 100644 index 000000000..f3ecd5556 --- /dev/null +++ b/freqtrade/loggers/rich_console.py @@ -0,0 +1,26 @@ +import sys +from shutil import get_terminal_size + +from rich.console import Console + + +def console_width() -> int | None: + """ + Get the width of the console + """ + if any(module in ["pytest", "ipykernel"] for module in sys.modules): + return 200 + + width, _ = get_terminal_size((1, 24)) + # Fall back to 200 if terminal size is not available. + # This is determined by assuming an insane width of 1char, which is unlikely. + w = None if width > 1 else 200 + return w + + +def get_rich_console(**kwargs) -> Console: + """ + Get a rich console with default settings + """ + kwargs["width"] = kwargs.get("width", console_width()) + return Console(**kwargs) diff --git a/freqtrade/util/progress_tracker.py b/freqtrade/util/progress_tracker.py index cc5c79a18..aac1418bc 100644 --- a/freqtrade/util/progress_tracker.py +++ b/freqtrade/util/progress_tracker.py @@ -7,7 +7,6 @@ from rich.progress import ( TimeRemainingColumn, ) -from freqtrade.loggers import error_console from freqtrade.util.rich_progress import CustomProgress @@ -21,6 +20,8 @@ def get_progress_tracker(**kwargs) -> CustomProgress: """ Get progress Bar with custom columns. """ + from freqtrade.loggers import error_console + return CustomProgress( TextColumn("[progress.description]{task.description}"), BarColumn(bar_width=None), diff --git a/freqtrade/util/rich_tables.py b/freqtrade/util/rich_tables.py index ba232ed75..093c73584 100644 --- a/freqtrade/util/rich_tables.py +++ b/freqtrade/util/rich_tables.py @@ -1,12 +1,12 @@ -import sys from collections.abc import Sequence from typing import Any, TypeAlias from pandas import DataFrame -from rich.console import Console from rich.table import Column, Table from rich.text import Text +from freqtrade.loggers.rich_console import get_rich_console + TextOrString: TypeAlias = str | Text @@ -38,11 +38,7 @@ def print_rich_table( row_to_add: list[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=width) + console = get_rich_console() console.print(table) @@ -74,9 +70,5 @@ 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=width) + console = get_rich_console() console.print(table) From e62a0f76edf6b81fdc8c5cbdcdcd01ca24c6ec26 Mon Sep 17 00:00:00 2001 From: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com> Date: Tue, 11 Mar 2025 03:11:15 +0000 Subject: [PATCH 18/23] 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 bb2d50511..74629a630 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.9.9' + rev: 'v0.9.10' hooks: - id: ruff - id: ruff-format @@ -70,6 +70,6 @@ repos: # Ensure github actions remain safe - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: v1.4.1 + rev: v1.5.0 hooks: - id: zizmor From 265a798f780e8bb8623dd47a887a0321611e7155 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 11 Mar 2025 06:37:44 +0100 Subject: [PATCH 19/23] docs: auto-generate command snippets --- docs/commands/backtesting-show.md | 7 ++++--- docs/commands/backtesting.md | 7 ++++--- docs/commands/hyperopt-show.md | 7 ++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/commands/backtesting-show.md b/docs/commands/backtesting-show.md index 1c7889851..a1b146a82 100644 --- a/docs/commands/backtesting-show.md +++ b/docs/commands/backtesting-show.md @@ -2,7 +2,7 @@ usage: freqtrade backtesting-show [-h] [-v] [--no-color] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--export-filename PATH] [--show-pair-list] - [--breakdown {day,week,month} [{day,week,month} ...]] + [--breakdown {day,week,month,year} [{day,week,month,year} ...]] options: -h, --help show this help message and exit @@ -11,8 +11,9 @@ options: `--export` to be set as well. Example: `--export-filen ame=user_data/backtest_results/backtest_today.json` --show-pair-list Show backtesting pairlist sorted by profit. - --breakdown {day,week,month} [{day,week,month} ...] - Show backtesting breakdown per [day, week, month]. + --breakdown {day,week,month,year} [{day,week,month,year} ...] + Show backtesting breakdown per [day, week, month, + year]. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). diff --git a/docs/commands/backtesting.md b/docs/commands/backtesting.md index 160bda7cc..395ab4b00 100644 --- a/docs/commands/backtesting.md +++ b/docs/commands/backtesting.md @@ -15,7 +15,7 @@ usage: freqtrade backtesting [-h] [-v] [--no-color] [--logfile FILE] [-V] [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] [--export {none,trades,signals}] [--export-filename PATH] - [--breakdown {day,week,month} [{day,week,month} ...]] + [--breakdown {day,week,month,year} [{day,week,month,year} ...]] [--cache {none,day,week,month}] [--freqai-backtest-live-models] @@ -65,8 +65,9 @@ options: Use this filename for backtest results.Requires `--export` to be set as well. Example: `--export-filen ame=user_data/backtest_results/backtest_today.json` - --breakdown {day,week,month} [{day,week,month} ...] - Show backtesting breakdown per [day, week, month]. + --breakdown {day,week,month,year} [{day,week,month,year} ...] + Show backtesting breakdown per [day, week, month, + year]. --cache {none,day,week,month} Load a cached backtest result no older than specified age (default: day). diff --git a/docs/commands/hyperopt-show.md b/docs/commands/hyperopt-show.md index 9c7c378ab..14d6516e9 100644 --- a/docs/commands/hyperopt-show.md +++ b/docs/commands/hyperopt-show.md @@ -4,7 +4,7 @@ usage: freqtrade hyperopt-show [-h] [-v] [--no-color] [--logfile FILE] [-V] [--profitable] [-n INT] [--print-json] [--hyperopt-filename FILENAME] [--no-header] [--disable-param-export] - [--breakdown {day,week,month} [{day,week,month} ...]] + [--breakdown {day,week,month,year} [{day,week,month,year} ...]] options: -h, --help show this help message and exit @@ -18,8 +18,9 @@ options: --no-header Do not print epoch details header. --disable-param-export Disable automatic hyperopt parameter export. - --breakdown {day,week,month} [{day,week,month} ...] - Show backtesting breakdown per [day, week, month]. + --breakdown {day,week,month,year} [{day,week,month,year} ...] + Show backtesting breakdown per [day, week, month, + year]. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). From 97d303579a882747dd155b1769f700a3ba0e7cca Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 11 Mar 2025 07:04:04 +0100 Subject: [PATCH 20/23] feat: add continual_learning to config schema --- freqtrade/configuration/config_schema.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py index 91e5c2d1b..c89895a04 100644 --- a/freqtrade/configuration/config_schema.py +++ b/freqtrade/configuration/config_schema.py @@ -1015,6 +1015,15 @@ CONF_SCHEMA = { "type": "boolean", "default": True, }, + "continual_learning": { + "description": ( + "Use the final state of the most recently trained model " + "as starting point for the new model, allowing for " + "incremental learning." + ), + "type": "boolean", + "default": False, + }, "feature_parameters": { "description": "The parameters used to engineer the feature set", "type": "object", From 5c01023d554aefeff97ec5ca3aa9b0d4b6759b62 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 11 Mar 2025 07:09:46 +0100 Subject: [PATCH 21/23] feat: add missing freqAI parameters to config schema --- freqtrade/configuration/config_schema.py | 56 ++++++++++++++++++++---- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py index c89895a04..f50334938 100644 --- a/freqtrade/configuration/config_schema.py +++ b/freqtrade/configuration/config_schema.py @@ -965,10 +965,13 @@ CONF_SCHEMA = { "type": "boolean", "default": False, }, - "keras": { - "description": "Use Keras for model training.", - "type": "boolean", - "default": False, + "identifier": { + "description": ( + "A unique ID for the current model. " + "Must be changed when modifying features." + ), + "type": "string", + "default": "example", }, "write_metrics_to_disk": { "description": "Write metrics to disk?", @@ -1000,13 +1003,43 @@ CONF_SCHEMA = { "type": "number", "default": 7, }, - "identifier": { + "live_retrain_hours": { + "description": "Frequency of retraining during dry/live runs.", + "type": "number", + "default": 0, + }, + "expiration_hours": { "description": ( - "A unique ID for the current model. " - "Must be changed when modifying features." + "Avoid making predictions if a model is more than `expiration_hours` " + "old. Defaults to 0 (no expiration)." ), - "type": "string", - "default": "example", + "type": "number", + "default": 0, + }, + "save_backtest_models": { + "description": "Save models to disk when running backtesting.", + "type": "boolean", + "default": False, + }, + "fit_live_predictions_candles": { + "description": ( + "Number of historical candles to use for computing target (label) " + "statistics from prediction data, instead of from the training dataset." + ), + "type": "boolean", + "default": False, + }, + "data_kitchen_thread_count": { + "description": ( + "Designate the number of threads you want to use for data processing " + "(outlier methods, normalization, etc.)." + ), + "type": "integer", + }, + "activate_tensorboard": { + "description": "Indicate whether or not to activate tensorboard", + "type": "boolean", + "default": True, }, "wait_for_training_iteration_on_reload": { "description": ( @@ -1024,6 +1057,11 @@ CONF_SCHEMA = { "type": "boolean", "default": False, }, + "keras": { + "description": "Use Keras for model training.", + "type": "boolean", + "default": False, + }, "feature_parameters": { "description": "The parameters used to engineer the feature set", "type": "object", From d69b1566b120559964f941ac6a6549150d8aefd5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 11 Mar 2025 07:10:37 +0100 Subject: [PATCH 22/23] chore: auto-build schema.json --- build_helpers/schema.json | 50 ++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/build_helpers/schema.json b/build_helpers/schema.json index 15764df90..dd82212b0 100644 --- a/build_helpers/schema.json +++ b/build_helpers/schema.json @@ -1366,10 +1366,10 @@ "type": "boolean", "default": false }, - "keras": { - "description": "Use Keras for model training.", - "type": "boolean", - "default": false + "identifier": { + "description": "A unique ID for the current model. Must be changed when modifying features.", + "type": "string", + "default": "example" }, "write_metrics_to_disk": { "description": "Write metrics to disk?", @@ -1399,16 +1399,50 @@ "type": "number", "default": 7 }, - "identifier": { - "description": "A unique ID for the current model. Must be changed when modifying features.", - "type": "string", - "default": "example" + "live_retrain_hours": { + "description": "Frequency of retraining during dry/live runs.", + "type": "number", + "default": 0 + }, + "expiration_hours": { + "description": "Avoid making predictions if a model is more than `expiration_hours` old. Defaults to 0 (no expiration).", + "type": "number", + "default": 0 + }, + "save_backtest_models": { + "description": "Save models to disk when running backtesting.", + "type": "boolean", + "default": false + }, + "fit_live_predictions_candles": { + "description": "Number of historical candles to use for computing target (label) statistics from prediction data, instead of from the training dataset.", + "type": "boolean", + "default": false + }, + "data_kitchen_thread_count": { + "description": "Designate the number of threads you want to use for data processing (outlier methods, normalization, etc.).", + "type": "integer" + }, + "activate_tensorboard": { + "description": "Indicate whether or not to activate tensorboard", + "type": "boolean", + "default": true }, "wait_for_training_iteration_on_reload": { "description": "Wait for the next training iteration to complete after /reload or ctrl+c.", "type": "boolean", "default": true }, + "continual_learning": { + "description": "Use the final state of the most recently trained model as starting point for the new model, allowing for incremental learning.", + "type": "boolean", + "default": false + }, + "keras": { + "description": "Use Keras for model training.", + "type": "boolean", + "default": false + }, "feature_parameters": { "description": "The parameters used to engineer the feature set", "type": "object", From 442b29e0eab8979b5f1c24adf4d3122547aec700 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 11 Mar 2025 20:01:21 +0100 Subject: [PATCH 23/23] chore: run schema export --- build_helpers/schema.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build_helpers/schema.json b/build_helpers/schema.json index 15764df90..4601b9728 100644 --- a/build_helpers/schema.json +++ b/build_helpers/schema.json @@ -257,7 +257,8 @@ "enum": [ "day", "week", - "month" + "month", + "year" ] } },