diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 263ad5db7..257890ae3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,9 +65,12 @@ jobs: export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH export TA_LIBRARY_PATH=${HOME}/dependencies/lib export TA_INCLUDE_PATH=${HOME}/dependencies/include + echo "numpy<2.3.0" > constraints.txt + export UV_BUILD_CONSTRAINT=constraints.txt uv pip install -r requirements-dev.txt uv pip install -e ft_client/ uv pip install -e . + rm constraints.txt - name: Check for version alignment run: | @@ -228,9 +231,12 @@ jobs: export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH export TA_LIBRARY_PATH=${HOME}/dependencies/lib export TA_INCLUDE_PATH=${HOME}/dependencies/include + echo "numpy<2.3.0" > constraints.txt + export UV_BUILD_CONSTRAINT=constraints.txt uv pip install -r requirements-dev.txt uv pip install -e ft_client/ uv pip install -e . + rm constraints.txt - name: Tests run: | @@ -480,9 +486,13 @@ jobs: export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH export TA_LIBRARY_PATH=${HOME}/dependencies/lib export TA_INCLUDE_PATH=${HOME}/dependencies/include + echo "numpy<2.3.0" > constraints.txt + export UV_BUILD_CONSTRAINT=constraints.txt uv pip install -r requirements-dev.txt uv pip install -e ft_client/ uv pip install -e . + rm constraints.txt + - name: Tests incl. ccxt compatibility tests env: diff --git a/Dockerfile b/Dockerfile index edc35b474..ec7dca342 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,7 +35,9 @@ ENV LD_LIBRARY_PATH /usr/local/lib # Install dependencies COPY --chown=ftuser:ftuser requirements.txt requirements-hyperopt.txt /freqtrade/ USER ftuser -RUN pip install --user --no-cache-dir "numpy<3.0" \ +RUN pip install --user --no-cache-dir "numpy<2.3.0" \ + && echo "numpy<2.3.0" > /tmp/constraints.txt \ + && export PIP_CONSTRAINT=/tmp/constraints.txt \ && pip install --user --no-cache-dir -r requirements-hyperopt.txt # Copy dependencies to runtime-image diff --git a/build_helpers/install_windows.ps1 b/build_helpers/install_windows.ps1 index 3cdbbb3f6..99f4de025 100644 --- a/build_helpers/install_windows.ps1 +++ b/build_helpers/install_windows.ps1 @@ -3,7 +3,7 @@ python -m pip install --upgrade pip python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" -pip install -U wheel "numpy<3" +pip install -U wheel "numpy<2.3" pip install --only-binary ta-lib --find-links=build_helpers\ ta-lib pip install -r requirements-dev.txt diff --git a/build_helpers/schema.json b/build_helpers/schema.json index a7323ddfa..b40465f26 100644 --- a/build_helpers/schema.json +++ b/build_helpers/schema.json @@ -1247,7 +1247,11 @@ "type": "object" }, "ccxt_async_config": { - "description": "CCXT asynchronous configuration settings.", + "description": "CCXT asynchronous configuration settings.Usually ccxt_config should be used instead.", + "type": "object" + }, + "ccxt_sync_config": { + "description": "CCXT synchronous configuration settings. Usually ccxt_config should be used instead.", "type": "object" } }, diff --git a/docker/Dockerfile.armhf b/docker/Dockerfile.armhf index 3c705180c..6167c0d07 100644 --- a/docker/Dockerfile.armhf +++ b/docker/Dockerfile.armhf @@ -34,7 +34,7 @@ COPY build_helpers/* /tmp/ # Install dependencies COPY --chown=ftuser:ftuser requirements.txt /freqtrade/ USER ftuser -RUN pip install --user --no-cache-dir "numpy<3" \ +RUN pip install --user --no-cache-dir "numpy<2.3.0" \ && pip install --user --no-index --find-links /tmp/ pyarrow TA-Lib \ && pip install --user --no-cache-dir -r requirements.txt diff --git a/docs/backtesting.md b/docs/backtesting.md index 4d91b3ca6..924edc2ac 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -5,6 +5,8 @@ This page explains how to validate your strategy performance by using Backtestin Backtesting requires historic data to be available. To learn how to get data for the pairs and exchange you're interested in, head over to the [Data Downloading](data-download.md) section of the documentation. +Backtesting is also available in [webserver mode](freq-ui.md#backtesting), which allows you to run backtests via the web interface. + ## Backtesting command reference --8<-- "commands/backtesting.md" @@ -435,6 +437,10 @@ To save time, by default backtest will reuse a cached result from within the las To further analyze your backtest results, freqtrade will export the trades to file by default. You can then load the trades to perform further analysis as shown in the [data analysis](strategy_analysis_example.md#load-backtest-results-to-pandas-dataframe) backtesting section. +Also, you can use freqtrade in [webserver mode](freq-ui.md#backtesting) to visualize the backtest results in a web interface. +This mode also allows you to load existing backtest results, so you can analyze them without running the backtest again. +For this mode - `--notes ""` can be used to add notes to the backtest results, which will be shown in the web interface. + ### Backtest output file The output file freqtrade produces is a zip file containing the following files: diff --git a/docs/commands/backtesting.md b/docs/commands/backtesting.md index c54d9e40c..8e09c245d 100644 --- a/docs/commands/backtesting.md +++ b/docs/commands/backtesting.md @@ -17,7 +17,7 @@ usage: freqtrade backtesting [-h] [-v] [--no-color] [--logfile FILE] [-V] [--export-filename PATH] [--breakdown {day,week,month,year} [{day,week,month,year} ...]] [--cache {none,day,week,month}] - [--freqai-backtest-live-models] + [--freqai-backtest-live-models] [--notes TEXT] options: -h, --help show this help message and exit @@ -73,6 +73,7 @@ options: age (default: day). --freqai-backtest-live-models Run backtest with ready models. + --notes TEXT Add notes to the backtest results. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 52beb3dfa..df5775183 100755 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -57,6 +57,7 @@ ARGS_BACKTEST = [ "backtest_breakdown", "backtest_cache", "freqai_backtest_live_models", + "backtest_notes", ] ARGS_HYPEROPT = [ @@ -250,7 +251,7 @@ ARGS_STRATEGY_UPDATER = ["strategy_list", "strategy_path", "recursive_strategy_s ARGS_LOOKAHEAD_ANALYSIS = [ a for a in ARGS_BACKTEST - if a not in ("position_stacking", "backtest_cache", "backtest_breakdown") + if a not in ("position_stacking", "backtest_cache", "backtest_breakdown", "backtest_notes") ] + ["minimum_trade_amount", "targeted_trade_amount", "lookahead_analysis_exportfilename"] ARGS_RECURSIVE_ANALYSIS = ["timeframe", "timerange", "dataformat_ohlcv", "pairs", "startup_candle"] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index c5d0ad49b..669f68f5a 100755 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -204,6 +204,11 @@ AVAILABLE_CLI_OPTIONS = { help="Export backtest results (default: trades).", choices=constants.EXPORT_OPTIONS, ), + "backtest_notes": Arg( + "--notes", + help="Add notes to the backtest results.", + metavar="TEXT", + ), "exportfilename": Arg( "--export-filename", "--backtest-filename", diff --git a/freqtrade/config_schema/config_schema.py b/freqtrade/config_schema/config_schema.py index 9c91d81f7..ce71e8f78 100644 --- a/freqtrade/config_schema/config_schema.py +++ b/freqtrade/config_schema/config_schema.py @@ -913,7 +913,17 @@ CONF_SCHEMA = { }, "ccxt_config": {"description": "CCXT configuration settings.", "type": "object"}, "ccxt_async_config": { - "description": "CCXT asynchronous configuration settings.", + "description": ( + "CCXT asynchronous configuration settings." + "Usually ccxt_config should be used instead." + ), + "type": "object", + }, + "ccxt_sync_config": { + "description": ( + "CCXT synchronous configuration settings. " + "Usually ccxt_config should be used instead." + ), "type": "object", }, }, diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 264d114b8..e1e62e778 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -310,6 +310,7 @@ class Configuration: ("backtest_cache", "Parameter --cache={} detected ..."), ("disableparamexport", "Parameter --disableparamexport detected: {} ..."), ("freqai_backtest_live_models", "Parameter --freqai-backtest-live-models detected ..."), + ("backtest_notes", "Parameter --notes detected: {} ..."), ] self._args_to_config_loop(config, configurations) diff --git a/freqtrade/configuration/environment_vars.py b/freqtrade/configuration/environment_vars.py index 8a825f59b..e5eb031e7 100644 --- a/freqtrade/configuration/environment_vars.py +++ b/freqtrade/configuration/environment_vars.py @@ -43,15 +43,27 @@ def _flat_vars_to_nested_dict(env_dict: dict[str, Any], prefix: str) -> dict[str :return: Nested dict based on available and relevant variables. """ no_convert = ["CHAT_ID", "PASSWORD"] + ccxt_config_keys = ["ccxt_config", "ccxt_sync_config", "ccxt_async_config"] relevant_vars: dict[str, Any] = {} for env_var, val in sorted(env_dict.items()): if env_var.startswith(prefix): logger.info(f"Loading variable '{env_var}'") key = env_var.replace(prefix, "") - for k in reversed(key.split("__")): + key_parts = key.split("__") + logger.info("Key parts: %s", key_parts) + + # Check if any ccxt config key is in the key parts + preserve_case = key_parts[0].lower() == "exchange" and any( + ccxt_key in [part.lower() for part in key_parts] for ccxt_key in ccxt_config_keys + ) + + for i, k in enumerate(reversed(key_parts)): + # Preserve case for the final key if ccxt config is involved + key_name = k if preserve_case and i == 0 else k.lower() + val = { - k.lower(): ( + key_name: ( _get_var_typed(val) if not isinstance(val, dict) and k not in no_convert else val diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 2d58c461a..ed247e20d 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1822,7 +1822,11 @@ class Backtesting: # Update old results with new ones. if len(self.all_bt_content) > 0: results = generate_backtest_stats( - data, self.all_bt_content, min_date=min_date, max_date=max_date + data, + self.all_bt_content, + min_date=min_date, + max_date=max_date, + notes=self.config.get("backtest_notes"), ) if self.results: self.results["metadata"].update(results["metadata"]) diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index 4242d7a9e..d35e96605 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -347,7 +347,7 @@ def generate_trading_stats(results: DataFrame) -> dict[str, Any]: else timedelta() ) winner_holding_min = ( - timedelta(minutes=round(winning_duration[winning_duration > 0].min())) + timedelta(minutes=round(winning_duration.min())) if not winning_duration.empty else timedelta() ) @@ -362,7 +362,7 @@ def generate_trading_stats(results: DataFrame) -> dict[str, Any]: else timedelta() ) loser_holding_min = ( - timedelta(minutes=round(losing_duration[losing_duration > 0].min())) + timedelta(minutes=round(losing_duration.min())) if not losing_duration.empty else timedelta() ) @@ -669,6 +669,7 @@ def generate_backtest_stats( all_results: dict[str, BacktestContentType], min_date: datetime, max_date: datetime, + notes: str | None = None, ) -> BacktestResultType: """ :param btdata: Backtest data @@ -694,6 +695,8 @@ def generate_backtest_stats( "backtest_start_ts": int(min_date.timestamp()), "backtest_end_ts": int(max_date.timestamp()), } + if notes: + metadata[strategy]["notes"] = notes result["strategy"][strategy] = strat_stats strategy_results = generate_strategy_comparison(bt_stats=result["strategy"]) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 6c54ad350..32ece7b4e 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1477,7 +1477,9 @@ def test_flat_vars_to_nested_dict(caplog): test_args = { "FREQTRADE__EXCHANGE__SOME_SETTING": "true", "FREQTRADE__EXCHANGE__SOME_FALSE_SETTING": "false", - "FREQTRADE__EXCHANGE__CONFIG__whatever": "sometime", + "FREQTRADE__EXCHANGE__CONFIG__whatEver": "sometime", # Lowercased + # Preserve case for ccxt_config + "FREQTRADE__EXCHANGE__CCXT_CONFIG__httpsProxy": "something", "FREQTRADE__EXIT_PRICING__PRICE_SIDE": "bid", "FREQTRADE__EXIT_PRICING__cccc": "500", "FREQTRADE__STAKE_AMOUNT": "200.05", @@ -1500,6 +1502,9 @@ def test_flat_vars_to_nested_dict(caplog): "config": { "whatever": "sometime", }, + "ccxt_config": { + "httpsProxy": "something", + }, "some_setting": True, "some_false_setting": False, "pair_whitelist": ["BTC/USDT", "ETH/USDT"],