Merge branch 'freqtrade:develop' into develop
This commit is contained in:
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
markdown==3.6
|
markdown==3.6
|
||||||
mkdocs==1.6.0
|
mkdocs==1.6.0
|
||||||
mkdocs-material==9.5.22
|
mkdocs-material==9.5.23
|
||||||
mdx_truly_sane_lists==1.3
|
mdx_truly_sane_lists==1.3
|
||||||
pymdown-extensions==10.8.1
|
pymdown-extensions==10.8.1
|
||||||
jinja2==3.1.4
|
jinja2==3.1.4
|
||||||
|
|||||||
@@ -17,4 +17,6 @@ class Bingx(Exchange):
|
|||||||
|
|
||||||
_ft_has: Dict = {
|
_ft_has: Dict = {
|
||||||
"ohlcv_candle_limit": 1000,
|
"ohlcv_candle_limit": 1000,
|
||||||
|
"stoploss_on_exchange": False,
|
||||||
|
"stoploss_order_types": {"limit": "limit", "market": "market"},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,23 +10,26 @@ individual needs.
|
|||||||
|
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
from freqtrade.constants import Config
|
||||||
from freqtrade.data.metrics import calculate_max_drawdown
|
from freqtrade.data.metrics import calculate_max_drawdown
|
||||||
from freqtrade.optimize.hyperopt import IHyperOptLoss
|
from freqtrade.optimize.hyperopt import IHyperOptLoss
|
||||||
|
|
||||||
|
|
||||||
# higher numbers penalize drawdowns more severely
|
# smaller numbers penalize drawdowns more severely
|
||||||
DRAWDOWN_MULT = 0.075
|
DRAWDOWN_MULT = 0.075
|
||||||
|
|
||||||
|
|
||||||
class ProfitDrawDownHyperOptLoss(IHyperOptLoss):
|
class ProfitDrawDownHyperOptLoss(IHyperOptLoss):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def hyperopt_loss_function(results: DataFrame, trade_count: int, *args, **kwargs) -> float:
|
def hyperopt_loss_function(results: DataFrame, config: Config, *args, **kwargs) -> float:
|
||||||
total_profit = results["profit_abs"].sum()
|
total_profit = results["profit_abs"].sum()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
drawdown = calculate_max_drawdown(results, value_col="profit_abs")
|
drawdown = calculate_max_drawdown(
|
||||||
|
results, starting_balance=config["dry_run_wallet"], value_col="profit_abs"
|
||||||
|
)
|
||||||
relative_account_drawdown = drawdown.relative_account_drawdown
|
relative_account_drawdown = drawdown.relative_account_drawdown
|
||||||
except ValueError:
|
except ValueError:
|
||||||
relative_account_drawdown = 0
|
relative_account_drawdown = 0
|
||||||
|
|
||||||
return -1 * (total_profit * (1 - relative_account_drawdown * DRAWDOWN_MULT))
|
return -1 * (total_profit - (relative_account_drawdown * total_profit) / DRAWDOWN_MULT)
|
||||||
|
|||||||
@@ -358,14 +358,15 @@ class HyperoptTools:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def prepare_trials_columns(trials: pd.DataFrame, has_drawdown: bool) -> pd.DataFrame:
|
def prepare_trials_columns(trials: pd.DataFrame) -> pd.DataFrame:
|
||||||
trials["Best"] = ""
|
trials["Best"] = ""
|
||||||
|
|
||||||
if "results_metrics.winsdrawslosses" not in trials.columns:
|
if "results_metrics.winsdrawslosses" not in trials.columns:
|
||||||
# Ensure compatibility with older versions of hyperopt results
|
# Ensure compatibility with older versions of hyperopt results
|
||||||
trials["results_metrics.winsdrawslosses"] = "N/A"
|
trials["results_metrics.winsdrawslosses"] = "N/A"
|
||||||
|
|
||||||
if not has_drawdown:
|
has_account_drawdown = "results_metrics.max_drawdown_account" in trials.columns
|
||||||
|
if not has_account_drawdown:
|
||||||
# Ensure compatibility with older versions of hyperopt results
|
# Ensure compatibility with older versions of hyperopt results
|
||||||
trials["results_metrics.max_drawdown_account"] = None
|
trials["results_metrics.max_drawdown_account"] = None
|
||||||
if "is_random" not in trials.columns:
|
if "is_random" not in trials.columns:
|
||||||
@@ -389,7 +390,6 @@ class HyperoptTools:
|
|||||||
"results_metrics.profit_total_abs",
|
"results_metrics.profit_total_abs",
|
||||||
"results_metrics.profit_total",
|
"results_metrics.profit_total",
|
||||||
"results_metrics.holding_avg",
|
"results_metrics.holding_avg",
|
||||||
"results_metrics.max_drawdown",
|
|
||||||
"results_metrics.max_drawdown_account",
|
"results_metrics.max_drawdown_account",
|
||||||
"results_metrics.max_drawdown_abs",
|
"results_metrics.max_drawdown_abs",
|
||||||
"loss",
|
"loss",
|
||||||
@@ -408,7 +408,6 @@ class HyperoptTools:
|
|||||||
"Total profit",
|
"Total profit",
|
||||||
"Profit",
|
"Profit",
|
||||||
"Avg duration",
|
"Avg duration",
|
||||||
"max_drawdown",
|
|
||||||
"max_drawdown_account",
|
"max_drawdown_account",
|
||||||
"max_drawdown_abs",
|
"max_drawdown_abs",
|
||||||
"Objective",
|
"Objective",
|
||||||
@@ -437,9 +436,7 @@ class HyperoptTools:
|
|||||||
tabulate.PRESERVE_WHITESPACE = True
|
tabulate.PRESERVE_WHITESPACE = True
|
||||||
trials = json_normalize(results, max_level=1)
|
trials = json_normalize(results, max_level=1)
|
||||||
|
|
||||||
has_account_drawdown = "results_metrics.max_drawdown_account" in trials.columns
|
trials = HyperoptTools.prepare_trials_columns(trials)
|
||||||
|
|
||||||
trials = HyperoptTools.prepare_trials_columns(trials, has_account_drawdown)
|
|
||||||
|
|
||||||
trials["is_profit"] = False
|
trials["is_profit"] = False
|
||||||
trials.loc[trials["is_initial_point"] | trials["is_random"], "Best"] = "* "
|
trials.loc[trials["is_initial_point"] | trials["is_random"], "Best"] = "* "
|
||||||
@@ -471,23 +468,19 @@ class HyperoptTools:
|
|||||||
|
|
||||||
stake_currency = config["stake_currency"]
|
stake_currency = config["stake_currency"]
|
||||||
|
|
||||||
trials[f"Max Drawdown{' (Acct)' if has_account_drawdown else ''}"] = trials.apply(
|
trials["Max Drawdown (Acct)"] = trials.apply(
|
||||||
lambda x: (
|
lambda x: (
|
||||||
"{} {}".format(
|
"{} {}".format(
|
||||||
fmt_coin(x["max_drawdown_abs"], stake_currency, keep_trailing_zeros=True),
|
fmt_coin(x["max_drawdown_abs"], stake_currency, keep_trailing_zeros=True),
|
||||||
(
|
(f"({x['max_drawdown_account']:,.2%})").rjust(10, " "),
|
||||||
f"({x['max_drawdown_account']:,.2%})"
|
|
||||||
if has_account_drawdown
|
|
||||||
else f"({x['max_drawdown']:,.2%})"
|
|
||||||
).rjust(10, " "),
|
|
||||||
).rjust(25 + len(stake_currency))
|
).rjust(25 + len(stake_currency))
|
||||||
if x["max_drawdown"] != 0.0 or x["max_drawdown_account"] != 0.0
|
if x["max_drawdown_account"] != 0.0
|
||||||
else "--".rjust(25 + len(stake_currency))
|
else "--".rjust(25 + len(stake_currency))
|
||||||
),
|
),
|
||||||
axis=1,
|
axis=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
trials = trials.drop(columns=["max_drawdown_abs", "max_drawdown", "max_drawdown_account"])
|
trials = trials.drop(columns=["max_drawdown_abs", "max_drawdown_account"])
|
||||||
|
|
||||||
trials["Profit"] = trials.apply(
|
trials["Profit"] = trials.apply(
|
||||||
lambda x: (
|
lambda x: (
|
||||||
|
|||||||
@@ -497,7 +497,6 @@ def generate_strategy_stats(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
max_drawdown_legacy = calculate_max_drawdown(results, value_col="profit_ratio")
|
|
||||||
drawdown = calculate_max_drawdown(
|
drawdown = calculate_max_drawdown(
|
||||||
results, value_col="profit_abs", starting_balance=start_balance
|
results, value_col="profit_abs", starting_balance=start_balance
|
||||||
)
|
)
|
||||||
@@ -508,7 +507,6 @@ def generate_strategy_stats(
|
|||||||
|
|
||||||
strat_stats.update(
|
strat_stats.update(
|
||||||
{
|
{
|
||||||
"max_drawdown": max_drawdown_legacy.drawdown_abs, # Deprecated - do not use
|
|
||||||
"max_drawdown_account": drawdown.relative_account_drawdown,
|
"max_drawdown_account": drawdown.relative_account_drawdown,
|
||||||
"max_relative_drawdown": underwater.relative_account_drawdown,
|
"max_relative_drawdown": underwater.relative_account_drawdown,
|
||||||
"max_drawdown_abs": drawdown.drawdown_abs,
|
"max_drawdown_abs": drawdown.drawdown_abs,
|
||||||
@@ -527,7 +525,6 @@ def generate_strategy_stats(
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
strat_stats.update(
|
strat_stats.update(
|
||||||
{
|
{
|
||||||
"max_drawdown": 0.0,
|
|
||||||
"max_drawdown_account": 0.0,
|
"max_drawdown_account": 0.0,
|
||||||
"max_relative_drawdown": 0.0,
|
"max_relative_drawdown": 0.0,
|
||||||
"max_drawdown_abs": 0.0,
|
"max_drawdown_abs": 0.0,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
from ipaddress import IPv4Address
|
from ipaddress import ip_address
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
@@ -180,7 +180,7 @@ class ApiServer(RPCHandler):
|
|||||||
rest_port = self._config["api_server"]["listen_port"]
|
rest_port = self._config["api_server"]["listen_port"]
|
||||||
|
|
||||||
logger.info(f"Starting HTTP Server at {rest_ip}:{rest_port}")
|
logger.info(f"Starting HTTP Server at {rest_ip}:{rest_port}")
|
||||||
if not IPv4Address(rest_ip).is_loopback and not running_in_docker():
|
if not ip_address(rest_ip).is_loopback and not running_in_docker():
|
||||||
logger.warning("SECURITY WARNING - Local Rest Server listening to external connections")
|
logger.warning("SECURITY WARNING - Local Rest Server listening to external connections")
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"SECURITY WARNING - This is insecure please set to your loopback,"
|
"SECURITY WARNING - This is insecure please set to your loopback,"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# Requirements for freqtrade client library
|
# Requirements for freqtrade client library
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
python-rapidjson==1.16
|
python-rapidjson==1.17
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
-r requirements-freqai-rl.txt
|
-r requirements-freqai-rl.txt
|
||||||
-r docs/requirements-docs.txt
|
-r docs/requirements-docs.txt
|
||||||
|
|
||||||
coveralls==4.0.0
|
coveralls==4.0.1
|
||||||
ruff==0.4.4
|
ruff==0.4.4
|
||||||
mypy==1.10.0
|
mypy==1.10.0
|
||||||
pre-commit==3.7.1
|
pre-commit==3.7.1
|
||||||
pytest==8.2.0
|
pytest==8.2.1
|
||||||
pytest-asyncio==0.23.6
|
pytest-asyncio==0.23.7
|
||||||
pytest-cov==5.0.0
|
pytest-cov==5.0.0
|
||||||
pytest-mock==3.14.0
|
pytest-mock==3.14.0
|
||||||
pytest-random-order==1.1.1
|
pytest-random-order==1.1.1
|
||||||
|
|||||||
+3
-3
@@ -2,7 +2,7 @@ numpy==1.26.4
|
|||||||
pandas==2.2.2
|
pandas==2.2.2
|
||||||
pandas-ta==0.3.14b
|
pandas-ta==0.3.14b
|
||||||
|
|
||||||
ccxt==4.3.24
|
ccxt==4.3.27
|
||||||
cryptography==42.0.7
|
cryptography==42.0.7
|
||||||
aiohttp==3.9.5
|
aiohttp==3.9.5
|
||||||
SQLAlchemy==2.0.30
|
SQLAlchemy==2.0.30
|
||||||
@@ -22,13 +22,13 @@ jinja2==3.1.4
|
|||||||
tables==3.9.1
|
tables==3.9.1
|
||||||
joblib==1.4.2
|
joblib==1.4.2
|
||||||
rich==13.7.1
|
rich==13.7.1
|
||||||
pyarrow==16.0.0; platform_machine != 'armv7l'
|
pyarrow==16.1.0; platform_machine != 'armv7l'
|
||||||
|
|
||||||
# find first, C search in arrays
|
# find first, C search in arrays
|
||||||
py_find_1st==1.1.6
|
py_find_1st==1.1.6
|
||||||
|
|
||||||
# Load ticker files 30% faster
|
# Load ticker files 30% faster
|
||||||
python-rapidjson==1.16
|
python-rapidjson==1.17
|
||||||
# Properly format api responses
|
# Properly format api responses
|
||||||
orjson==3.10.3
|
orjson==3.10.3
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ from tests.conftest import (
|
|||||||
patch_exchange,
|
patch_exchange,
|
||||||
patched_configuration_load_config_file,
|
patched_configuration_load_config_file,
|
||||||
)
|
)
|
||||||
|
from tests.conftest_hyperopt import hyperopt_test_result
|
||||||
from tests.conftest_trades import MOCK_TRADE_COUNT
|
from tests.conftest_trades import MOCK_TRADE_COUNT
|
||||||
|
|
||||||
|
|
||||||
@@ -1137,7 +1138,8 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
|||||||
pytest.fail(f"Expected well formed JSON, but failed to parse: {captured.out}")
|
pytest.fail(f"Expected well formed JSON, but failed to parse: {captured.out}")
|
||||||
|
|
||||||
|
|
||||||
def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmp_path):
|
def test_hyperopt_list(mocker, capsys, caplog, tmp_path):
|
||||||
|
saved_hyperopt_results = hyperopt_test_result()
|
||||||
csv_file = tmp_path / "test.csv"
|
csv_file = tmp_path / "test.csv"
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
"freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist",
|
"freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist",
|
||||||
@@ -1507,7 +1509,8 @@ def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmp_path)
|
|||||||
csv_file.unlink()
|
csv_file.unlink()
|
||||||
|
|
||||||
|
|
||||||
def test_hyperopt_show(mocker, capsys, saved_hyperopt_results):
|
def test_hyperopt_show(mocker, capsys):
|
||||||
|
saved_hyperopt_results = hyperopt_test_result()
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
"freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist",
|
"freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -47,7 +47,7 @@ def generate_result_metrics():
|
|||||||
"profit_total_abs": 0.001,
|
"profit_total_abs": 0.001,
|
||||||
"profit_total": 0.01,
|
"profit_total": 0.01,
|
||||||
"holding_avg": timedelta(minutes=20),
|
"holding_avg": timedelta(minutes=20),
|
||||||
"max_drawdown": 0.001,
|
"max_drawdown_account": 0.001,
|
||||||
"max_drawdown_abs": 0.001,
|
"max_drawdown_abs": 0.001,
|
||||||
"loss": 0.001,
|
"loss": 0.001,
|
||||||
"is_initial_point": 0.001,
|
"is_initial_point": 0.001,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class HyperoptableStrategy(StrategyTestV3):
|
|||||||
bot_loop_started = False
|
bot_loop_started = False
|
||||||
bot_started = False
|
bot_started = False
|
||||||
|
|
||||||
def bot_loop_start(self):
|
def bot_loop_start(self, **kwargs):
|
||||||
self.bot_loop_started = True
|
self.bot_loop_started = True
|
||||||
|
|
||||||
def bot_start(self, **kwargs) -> None:
|
def bot_start(self, **kwargs) -> None:
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class HyperoptableStrategyV2(StrategyTestV2):
|
|||||||
|
|
||||||
bot_loop_started = False
|
bot_loop_started = False
|
||||||
|
|
||||||
def bot_loop_start(self):
|
def bot_loop_start(self, **kwargs):
|
||||||
self.bot_loop_started = True
|
self.bot_loop_started = True
|
||||||
|
|
||||||
def bot_start(self, **kwargs) -> None:
|
def bot_start(self, **kwargs) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user