mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-11-29 08:33:07 +00:00
Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"])
|
||||||
|
|||||||
@@ -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"])
|
||||||
|
|||||||
@@ -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"],
|
||||||
|
|||||||
Reference in New Issue
Block a user