Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
hippocritical
2025-06-08 19:33:50 +02:00
15 changed files with 77 additions and 13 deletions

View File

@@ -65,9 +65,12 @@ jobs:
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
export TA_LIBRARY_PATH=${HOME}/dependencies/lib export TA_LIBRARY_PATH=${HOME}/dependencies/lib
export TA_INCLUDE_PATH=${HOME}/dependencies/include 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 -r requirements-dev.txt
uv pip install -e ft_client/ uv pip install -e ft_client/
uv pip install -e . uv pip install -e .
rm constraints.txt
- name: Check for version alignment - name: Check for version alignment
run: | run: |
@@ -228,9 +231,12 @@ jobs:
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
export TA_LIBRARY_PATH=${HOME}/dependencies/lib export TA_LIBRARY_PATH=${HOME}/dependencies/lib
export TA_INCLUDE_PATH=${HOME}/dependencies/include 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 -r requirements-dev.txt
uv pip install -e ft_client/ uv pip install -e ft_client/
uv pip install -e . uv pip install -e .
rm constraints.txt
- name: Tests - name: Tests
run: | run: |
@@ -480,9 +486,13 @@ jobs:
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
export TA_LIBRARY_PATH=${HOME}/dependencies/lib export TA_LIBRARY_PATH=${HOME}/dependencies/lib
export TA_INCLUDE_PATH=${HOME}/dependencies/include 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 -r requirements-dev.txt
uv pip install -e ft_client/ uv pip install -e ft_client/
uv pip install -e . uv pip install -e .
rm constraints.txt
- name: Tests incl. ccxt compatibility tests - name: Tests incl. ccxt compatibility tests
env: env:

View File

@@ -35,7 +35,9 @@ ENV LD_LIBRARY_PATH /usr/local/lib
# Install dependencies # Install dependencies
COPY --chown=ftuser:ftuser requirements.txt requirements-hyperopt.txt /freqtrade/ COPY --chown=ftuser:ftuser requirements.txt requirements-hyperopt.txt /freqtrade/
USER ftuser 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 && pip install --user --no-cache-dir -r requirements-hyperopt.txt
# Copy dependencies to runtime-image # Copy dependencies to runtime-image

View File

@@ -3,7 +3,7 @@
python -m pip install --upgrade pip python -m pip install --upgrade pip
python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 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 --only-binary ta-lib --find-links=build_helpers\ ta-lib
pip install -r requirements-dev.txt pip install -r requirements-dev.txt

View File

@@ -1247,7 +1247,11 @@
"type": "object" "type": "object"
}, },
"ccxt_async_config": { "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" "type": "object"
} }
}, },

View File

@@ -34,7 +34,7 @@ COPY build_helpers/* /tmp/
# Install dependencies # Install dependencies
COPY --chown=ftuser:ftuser requirements.txt /freqtrade/ COPY --chown=ftuser:ftuser requirements.txt /freqtrade/
USER ftuser 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-index --find-links /tmp/ pyarrow TA-Lib \
&& pip install --user --no-cache-dir -r requirements.txt && pip install --user --no-cache-dir -r requirements.txt

View File

@@ -5,6 +5,8 @@ This page explains how to validate your strategy performance by using Backtestin
Backtesting requires historic data to be available. 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. 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 ## Backtesting command reference
--8<-- "commands/backtesting.md" --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. 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. 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 "<notes>"` can be used to add notes to the backtest results, which will be shown in the web interface.
### Backtest output file ### Backtest output file
The output file freqtrade produces is a zip file containing the following files: The output file freqtrade produces is a zip file containing the following files:

View File

@@ -17,7 +17,7 @@ usage: freqtrade backtesting [-h] [-v] [--no-color] [--logfile FILE] [-V]
[--export-filename PATH] [--export-filename PATH]
[--breakdown {day,week,month,year} [{day,week,month,year} ...]] [--breakdown {day,week,month,year} [{day,week,month,year} ...]]
[--cache {none,day,week,month}] [--cache {none,day,week,month}]
[--freqai-backtest-live-models] [--freqai-backtest-live-models] [--notes TEXT]
options: options:
-h, --help show this help message and exit -h, --help show this help message and exit
@@ -73,6 +73,7 @@ options:
age (default: day). age (default: day).
--freqai-backtest-live-models --freqai-backtest-live-models
Run backtest with ready models. Run backtest with ready models.
--notes TEXT Add notes to the backtest results.
Common arguments: Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -v, --verbose Verbose mode (-vv for more, -vvv to get all messages).

View File

@@ -57,6 +57,7 @@ ARGS_BACKTEST = [
"backtest_breakdown", "backtest_breakdown",
"backtest_cache", "backtest_cache",
"freqai_backtest_live_models", "freqai_backtest_live_models",
"backtest_notes",
] ]
ARGS_HYPEROPT = [ ARGS_HYPEROPT = [
@@ -250,7 +251,7 @@ ARGS_STRATEGY_UPDATER = ["strategy_list", "strategy_path", "recursive_strategy_s
ARGS_LOOKAHEAD_ANALYSIS = [ ARGS_LOOKAHEAD_ANALYSIS = [
a a
for a in ARGS_BACKTEST 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"] ] + ["minimum_trade_amount", "targeted_trade_amount", "lookahead_analysis_exportfilename"]
ARGS_RECURSIVE_ANALYSIS = ["timeframe", "timerange", "dataformat_ohlcv", "pairs", "startup_candle"] ARGS_RECURSIVE_ANALYSIS = ["timeframe", "timerange", "dataformat_ohlcv", "pairs", "startup_candle"]

View File

@@ -204,6 +204,11 @@ AVAILABLE_CLI_OPTIONS = {
help="Export backtest results (default: trades).", help="Export backtest results (default: trades).",
choices=constants.EXPORT_OPTIONS, choices=constants.EXPORT_OPTIONS,
), ),
"backtest_notes": Arg(
"--notes",
help="Add notes to the backtest results.",
metavar="TEXT",
),
"exportfilename": Arg( "exportfilename": Arg(
"--export-filename", "--export-filename",
"--backtest-filename", "--backtest-filename",

View File

@@ -913,7 +913,17 @@ CONF_SCHEMA = {
}, },
"ccxt_config": {"description": "CCXT configuration settings.", "type": "object"}, "ccxt_config": {"description": "CCXT configuration settings.", "type": "object"},
"ccxt_async_config": { "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", "type": "object",
}, },
}, },

View File

@@ -310,6 +310,7 @@ class Configuration:
("backtest_cache", "Parameter --cache={} detected ..."), ("backtest_cache", "Parameter --cache={} detected ..."),
("disableparamexport", "Parameter --disableparamexport detected: {} ..."), ("disableparamexport", "Parameter --disableparamexport detected: {} ..."),
("freqai_backtest_live_models", "Parameter --freqai-backtest-live-models detected ..."), ("freqai_backtest_live_models", "Parameter --freqai-backtest-live-models detected ..."),
("backtest_notes", "Parameter --notes detected: {} ..."),
] ]
self._args_to_config_loop(config, configurations) self._args_to_config_loop(config, configurations)

View File

@@ -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. :return: Nested dict based on available and relevant variables.
""" """
no_convert = ["CHAT_ID", "PASSWORD"] no_convert = ["CHAT_ID", "PASSWORD"]
ccxt_config_keys = ["ccxt_config", "ccxt_sync_config", "ccxt_async_config"]
relevant_vars: dict[str, Any] = {} relevant_vars: dict[str, Any] = {}
for env_var, val in sorted(env_dict.items()): for env_var, val in sorted(env_dict.items()):
if env_var.startswith(prefix): if env_var.startswith(prefix):
logger.info(f"Loading variable '{env_var}'") logger.info(f"Loading variable '{env_var}'")
key = env_var.replace(prefix, "") 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 = { val = {
k.lower(): ( key_name: (
_get_var_typed(val) _get_var_typed(val)
if not isinstance(val, dict) and k not in no_convert if not isinstance(val, dict) and k not in no_convert
else val else val

View File

@@ -1822,7 +1822,11 @@ class Backtesting:
# Update old results with new ones. # Update old results with new ones.
if len(self.all_bt_content) > 0: if len(self.all_bt_content) > 0:
results = generate_backtest_stats( 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: if self.results:
self.results["metadata"].update(results["metadata"]) self.results["metadata"].update(results["metadata"])

View File

@@ -347,7 +347,7 @@ def generate_trading_stats(results: DataFrame) -> dict[str, Any]:
else timedelta() else timedelta()
) )
winner_holding_min = ( winner_holding_min = (
timedelta(minutes=round(winning_duration[winning_duration > 0].min())) timedelta(minutes=round(winning_duration.min()))
if not winning_duration.empty if not winning_duration.empty
else timedelta() else timedelta()
) )
@@ -362,7 +362,7 @@ def generate_trading_stats(results: DataFrame) -> dict[str, Any]:
else timedelta() else timedelta()
) )
loser_holding_min = ( loser_holding_min = (
timedelta(minutes=round(losing_duration[losing_duration > 0].min())) timedelta(minutes=round(losing_duration.min()))
if not losing_duration.empty if not losing_duration.empty
else timedelta() else timedelta()
) )
@@ -669,6 +669,7 @@ def generate_backtest_stats(
all_results: dict[str, BacktestContentType], all_results: dict[str, BacktestContentType],
min_date: datetime, min_date: datetime,
max_date: datetime, max_date: datetime,
notes: str | None = None,
) -> BacktestResultType: ) -> BacktestResultType:
""" """
:param btdata: Backtest data :param btdata: Backtest data
@@ -694,6 +695,8 @@ def generate_backtest_stats(
"backtest_start_ts": int(min_date.timestamp()), "backtest_start_ts": int(min_date.timestamp()),
"backtest_end_ts": int(max_date.timestamp()), "backtest_end_ts": int(max_date.timestamp()),
} }
if notes:
metadata[strategy]["notes"] = notes
result["strategy"][strategy] = strat_stats result["strategy"][strategy] = strat_stats
strategy_results = generate_strategy_comparison(bt_stats=result["strategy"]) strategy_results = generate_strategy_comparison(bt_stats=result["strategy"])

View File

@@ -1477,7 +1477,9 @@ def test_flat_vars_to_nested_dict(caplog):
test_args = { test_args = {
"FREQTRADE__EXCHANGE__SOME_SETTING": "true", "FREQTRADE__EXCHANGE__SOME_SETTING": "true",
"FREQTRADE__EXCHANGE__SOME_FALSE_SETTING": "false", "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__PRICE_SIDE": "bid",
"FREQTRADE__EXIT_PRICING__cccc": "500", "FREQTRADE__EXIT_PRICING__cccc": "500",
"FREQTRADE__STAKE_AMOUNT": "200.05", "FREQTRADE__STAKE_AMOUNT": "200.05",
@@ -1500,6 +1502,9 @@ def test_flat_vars_to_nested_dict(caplog):
"config": { "config": {
"whatever": "sometime", "whatever": "sometime",
}, },
"ccxt_config": {
"httpsProxy": "something",
},
"some_setting": True, "some_setting": True,
"some_false_setting": False, "some_false_setting": False,
"pair_whitelist": ["BTC/USDT", "ETH/USDT"], "pair_whitelist": ["BTC/USDT", "ETH/USDT"],