Merge branch 'freqtrade:develop' into develop
This commit is contained in:
+14
-10
@@ -38,8 +38,9 @@ jobs:
|
|||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6.0.0
|
||||||
with:
|
with:
|
||||||
|
activate-environment: true
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
cache-dependency-glob: "requirements**.txt"
|
cache-dependency-glob: "requirements**.txt"
|
||||||
@@ -144,7 +145,7 @@ jobs:
|
|||||||
mypy freqtrade scripts tests
|
mypy freqtrade scripts tests
|
||||||
|
|
||||||
- name: Discord notification
|
- name: Discord notification
|
||||||
uses: rjstone/discord-webhook-notify@89b0bf43c2c8514f70d0dcba4a706b904e8a3112 #v1.0.4
|
uses: rjstone/discord-webhook-notify@1399c1b2d57cc05894d506d2cfdc33c5f012b993 #v1.1.1
|
||||||
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||||
with:
|
with:
|
||||||
severity: error
|
severity: error
|
||||||
@@ -170,8 +171,9 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6.0.0
|
||||||
with:
|
with:
|
||||||
|
activate-environment: true
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
cache-dependency-glob: "requirements**.txt"
|
cache-dependency-glob: "requirements**.txt"
|
||||||
@@ -270,7 +272,7 @@ jobs:
|
|||||||
mypy freqtrade scripts
|
mypy freqtrade scripts
|
||||||
|
|
||||||
- name: Discord notification
|
- name: Discord notification
|
||||||
uses: rjstone/discord-webhook-notify@89b0bf43c2c8514f70d0dcba4a706b904e8a3112 #v1.0.4
|
uses: rjstone/discord-webhook-notify@1399c1b2d57cc05894d506d2cfdc33c5f012b993 #v1.1.1
|
||||||
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||||
with:
|
with:
|
||||||
severity: info
|
severity: info
|
||||||
@@ -296,8 +298,9 @@ jobs:
|
|||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6.0.0
|
||||||
with:
|
with:
|
||||||
|
activate-environment: true
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
cache-dependency-glob: "requirements**.txt"
|
cache-dependency-glob: "requirements**.txt"
|
||||||
@@ -363,7 +366,7 @@ jobs:
|
|||||||
shell: powershell
|
shell: powershell
|
||||||
|
|
||||||
- name: Discord notification
|
- name: Discord notification
|
||||||
uses: rjstone/discord-webhook-notify@89b0bf43c2c8514f70d0dcba4a706b904e8a3112 #v1.0.4
|
uses: rjstone/discord-webhook-notify@1399c1b2d57cc05894d506d2cfdc33c5f012b993 #v1.1.1
|
||||||
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||||
with:
|
with:
|
||||||
severity: error
|
severity: error
|
||||||
@@ -421,7 +424,7 @@ jobs:
|
|||||||
mkdocs build
|
mkdocs build
|
||||||
|
|
||||||
- name: Discord notification
|
- name: Discord notification
|
||||||
uses: rjstone/discord-webhook-notify@89b0bf43c2c8514f70d0dcba4a706b904e8a3112 #v1.0.4
|
uses: rjstone/discord-webhook-notify@1399c1b2d57cc05894d506d2cfdc33c5f012b993 #v1.1.1
|
||||||
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||||
with:
|
with:
|
||||||
severity: error
|
severity: error
|
||||||
@@ -443,8 +446,9 @@ jobs:
|
|||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
|
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6.0.0
|
||||||
with:
|
with:
|
||||||
|
activate-environment: true
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
cache-dependency-glob: "requirements**.txt"
|
cache-dependency-glob: "requirements**.txt"
|
||||||
@@ -508,7 +512,7 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Discord notification
|
- name: Discord notification
|
||||||
uses: rjstone/discord-webhook-notify@89b0bf43c2c8514f70d0dcba4a706b904e8a3112 #v1.0.4
|
uses: rjstone/discord-webhook-notify@1399c1b2d57cc05894d506d2cfdc33c5f012b993 #v1.1.1
|
||||||
if: always() && steps.check.outputs.has-permission && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
if: always() && steps.check.outputs.has-permission && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||||
with:
|
with:
|
||||||
severity: info
|
severity: info
|
||||||
@@ -703,7 +707,7 @@ jobs:
|
|||||||
build_helpers/publish_docker_arm64.sh
|
build_helpers/publish_docker_arm64.sh
|
||||||
|
|
||||||
- name: Discord notification
|
- name: Discord notification
|
||||||
uses: rjstone/discord-webhook-notify@89b0bf43c2c8514f70d0dcba4a706b904e8a3112 #v1.0.4
|
uses: rjstone/discord-webhook-notify@1399c1b2d57cc05894d506d2cfdc33c5f012b993 #v1.1.1
|
||||||
if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) && (github.event_name != 'schedule')
|
if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) && (github.event_name != 'schedule')
|
||||||
with:
|
with:
|
||||||
severity: info
|
severity: info
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -2,6 +2,6 @@ markdown==3.8
|
|||||||
mkdocs==1.6.1
|
mkdocs==1.6.1
|
||||||
mkdocs-material==9.6.12
|
mkdocs-material==9.6.12
|
||||||
mdx_truly_sane_lists==1.3
|
mdx_truly_sane_lists==1.3
|
||||||
pymdown-extensions==10.14.3
|
pymdown-extensions==10.15
|
||||||
jinja2==3.1.6
|
jinja2==3.1.6
|
||||||
mike==2.1.3
|
mike==2.1.3
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# flake8: noqa: F401
|
||||||
|
from .bt_fileutils import (
|
||||||
|
BT_DATA_COLUMNS,
|
||||||
|
delete_backtest_result,
|
||||||
|
extract_trades_of_period,
|
||||||
|
find_existing_backtest_stats,
|
||||||
|
get_backtest_market_change,
|
||||||
|
get_backtest_result,
|
||||||
|
get_backtest_resultlist,
|
||||||
|
get_latest_backtest_filename,
|
||||||
|
get_latest_hyperopt_file,
|
||||||
|
get_latest_hyperopt_filename,
|
||||||
|
get_latest_optimize_filename,
|
||||||
|
load_and_merge_backtest_result,
|
||||||
|
load_backtest_analysis_data,
|
||||||
|
load_backtest_data,
|
||||||
|
load_backtest_metadata,
|
||||||
|
load_backtest_stats,
|
||||||
|
load_exit_signal_candles,
|
||||||
|
load_file_from_zip,
|
||||||
|
load_rejected_signals,
|
||||||
|
load_signal_candles,
|
||||||
|
load_trades,
|
||||||
|
load_trades_from_db,
|
||||||
|
trade_list_to_dataframe,
|
||||||
|
update_backtest_metadata,
|
||||||
|
)
|
||||||
|
from .trade_parallelism import (
|
||||||
|
analyze_trade_parallelism,
|
||||||
|
evaluate_result_multi,
|
||||||
|
)
|
||||||
@@ -13,7 +13,7 @@ from typing import Any, Literal
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from freqtrade.constants import LAST_BT_RESULT_FN, IntOrInf
|
from freqtrade.constants import LAST_BT_RESULT_FN
|
||||||
from freqtrade.exceptions import ConfigurationError, OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
from freqtrade.ft_types import BacktestHistoryEntryType, BacktestResultType
|
from freqtrade.ft_types import BacktestHistoryEntryType, BacktestResultType
|
||||||
from freqtrade.misc import file_dump_json, json_load
|
from freqtrade.misc import file_dump_json, json_load
|
||||||
@@ -491,55 +491,6 @@ def load_exit_signal_candles(backtest_dir: Path) -> dict[str, dict[str, pd.DataF
|
|||||||
return load_backtest_analysis_data(backtest_dir, "exited")
|
return load_backtest_analysis_data(backtest_dir, "exited")
|
||||||
|
|
||||||
|
|
||||||
def analyze_trade_parallelism(results: pd.DataFrame, timeframe: str) -> pd.DataFrame:
|
|
||||||
"""
|
|
||||||
Find overlapping trades by expanding each trade once per period it was open
|
|
||||||
and then counting overlaps.
|
|
||||||
:param results: Results Dataframe - can be loaded
|
|
||||||
:param timeframe: Timeframe used for backtest
|
|
||||||
:return: dataframe with open-counts per time-period in timeframe
|
|
||||||
"""
|
|
||||||
from freqtrade.exchange import timeframe_to_resample_freq
|
|
||||||
|
|
||||||
timeframe_freq = timeframe_to_resample_freq(timeframe)
|
|
||||||
dates = [
|
|
||||||
pd.Series(
|
|
||||||
pd.date_range(
|
|
||||||
row[1]["open_date"],
|
|
||||||
row[1]["close_date"],
|
|
||||||
freq=timeframe_freq,
|
|
||||||
# Exclude right boundary - the date is the candle open date.
|
|
||||||
inclusive="left",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for row in results[["open_date", "close_date"]].iterrows()
|
|
||||||
]
|
|
||||||
deltas = [len(x) for x in dates]
|
|
||||||
dates = pd.Series(pd.concat(dates).values, name="date")
|
|
||||||
df2 = pd.DataFrame(np.repeat(results.values, deltas, axis=0), columns=results.columns)
|
|
||||||
|
|
||||||
df2 = pd.concat([dates, df2], axis=1)
|
|
||||||
df2 = df2.set_index("date")
|
|
||||||
df_final = df2.resample(timeframe_freq)[["pair"]].count()
|
|
||||||
df_final = df_final.rename({"pair": "open_trades"}, axis=1)
|
|
||||||
return df_final
|
|
||||||
|
|
||||||
|
|
||||||
def evaluate_result_multi(
|
|
||||||
results: pd.DataFrame, timeframe: str, max_open_trades: IntOrInf
|
|
||||||
) -> pd.DataFrame:
|
|
||||||
"""
|
|
||||||
Find overlapping trades by expanding each trade once per period it was open
|
|
||||||
and then counting overlaps
|
|
||||||
:param results: Results Dataframe - can be loaded
|
|
||||||
:param timeframe: Frequency used for the backtest
|
|
||||||
:param max_open_trades: parameter max_open_trades used during backtest run
|
|
||||||
:return: dataframe with open-counts per time-period in freq
|
|
||||||
"""
|
|
||||||
df_final = analyze_trade_parallelism(results, timeframe)
|
|
||||||
return df_final[df_final["open_trades"] > max_open_trades]
|
|
||||||
|
|
||||||
|
|
||||||
def trade_list_to_dataframe(trades: list[Trade] | list[LocalTrade]) -> pd.DataFrame:
|
def trade_list_to_dataframe(trades: list[Trade] | list[LocalTrade]) -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
Convert list of Trade objects to pandas Dataframe
|
Convert list of Trade objects to pandas Dataframe
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from freqtrade.constants import IntOrInf
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_trade_parallelism(trades: pd.DataFrame, timeframe: str) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Find overlapping trades by expanding each trade once per period it was open
|
||||||
|
and then counting overlaps.
|
||||||
|
:param trades: Trades Dataframe - can be loaded from backtest, or created
|
||||||
|
via trade_list_to_dataframe
|
||||||
|
:param timeframe: Timeframe used for backtest
|
||||||
|
:return: dataframe with open-counts per time-period in timeframe
|
||||||
|
"""
|
||||||
|
from freqtrade.exchange import timeframe_to_resample_freq
|
||||||
|
|
||||||
|
timeframe_freq = timeframe_to_resample_freq(timeframe)
|
||||||
|
dates = [
|
||||||
|
pd.Series(
|
||||||
|
pd.date_range(
|
||||||
|
row[1]["open_date"],
|
||||||
|
row[1]["close_date"],
|
||||||
|
freq=timeframe_freq,
|
||||||
|
# Exclude right boundary - the date is the candle open date.
|
||||||
|
inclusive="left",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for row in trades[["open_date", "close_date"]].iterrows()
|
||||||
|
]
|
||||||
|
deltas = [len(x) for x in dates]
|
||||||
|
dates = pd.Series(pd.concat(dates).values, name="date")
|
||||||
|
df2 = pd.DataFrame(np.repeat(trades.values, deltas, axis=0), columns=trades.columns)
|
||||||
|
|
||||||
|
df2 = pd.concat([dates, df2], axis=1)
|
||||||
|
df2 = df2.set_index("date")
|
||||||
|
df_final = df2.resample(timeframe_freq)[["pair"]].count()
|
||||||
|
df_final = df_final.rename({"pair": "open_trades"}, axis=1)
|
||||||
|
return df_final
|
||||||
|
|
||||||
|
|
||||||
|
def evaluate_result_multi(
|
||||||
|
trades: pd.DataFrame, timeframe: str, max_open_trades: IntOrInf
|
||||||
|
) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Find overlapping trades by expanding each trade once per period it was open
|
||||||
|
and then counting overlaps
|
||||||
|
:param trades: Trades Dataframe - can be loaded from backtest, or created
|
||||||
|
via trade_list_to_dataframe
|
||||||
|
:param timeframe: Frequency used for the backtest
|
||||||
|
:param max_open_trades: parameter max_open_trades used during backtest run
|
||||||
|
:return: dataframe with open-counts per time-period in freq
|
||||||
|
"""
|
||||||
|
df_final = analyze_trade_parallelism(trades, timeframe)
|
||||||
|
return df_final[df_final["open_trades"] > max_open_trades]
|
||||||
@@ -123,7 +123,7 @@ class Backtesting:
|
|||||||
config["dry_run"] = True
|
config["dry_run"] = True
|
||||||
self.run_ids: dict[str, str] = {}
|
self.run_ids: dict[str, str] = {}
|
||||||
self.strategylist: list[IStrategy] = []
|
self.strategylist: list[IStrategy] = []
|
||||||
self.all_results: dict[str, BacktestContentType] = {}
|
self.all_bt_content: dict[str, BacktestContentType] = {}
|
||||||
self.analysis_results: dict[str, dict[str, DataFrame]] = {
|
self.analysis_results: dict[str, dict[str, DataFrame]] = {
|
||||||
"signals": {},
|
"signals": {},
|
||||||
"rejected": {},
|
"rejected": {},
|
||||||
@@ -1717,7 +1717,7 @@ class Backtesting:
|
|||||||
"backtest_end_time": int(backtest_end_time.timestamp()),
|
"backtest_end_time": int(backtest_end_time.timestamp()),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.all_results[strategy_name] = results
|
self.all_bt_content[strategy_name] = results
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.config.get("export", "none") == "signals"
|
self.config.get("export", "none") == "signals"
|
||||||
@@ -1780,9 +1780,9 @@ class Backtesting:
|
|||||||
min_date, max_date = self.backtest_one_strategy(strat, data, timerange)
|
min_date, max_date = self.backtest_one_strategy(strat, data, timerange)
|
||||||
|
|
||||||
# Update old results with new ones.
|
# Update old results with new ones.
|
||||||
if len(self.all_results) > 0:
|
if len(self.all_bt_content) > 0:
|
||||||
results = generate_backtest_stats(
|
results = generate_backtest_stats(
|
||||||
data, self.all_results, min_date=min_date, max_date=max_date
|
data, self.all_bt_content, min_date=min_date, max_date=max_date
|
||||||
)
|
)
|
||||||
if self.results:
|
if self.results:
|
||||||
self.results["metadata"].update(results["metadata"])
|
self.results["metadata"].update(results["metadata"])
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import ClassVar
|
from typing import ClassVar, Literal
|
||||||
|
|
||||||
from sqlalchemy import String
|
from sqlalchemy import String
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
@@ -18,9 +18,11 @@ class ValueTypesEnum(str, Enum):
|
|||||||
INT = "int"
|
INT = "int"
|
||||||
|
|
||||||
|
|
||||||
class KeyStoreKeys(str, Enum):
|
KeyStoreKeys = Literal[
|
||||||
BOT_START_TIME = "bot_start_time"
|
"bot_start_time",
|
||||||
STARTUP_TIME = "startup_time"
|
"startup_time",
|
||||||
|
"binance_migration",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class _KeyValueStoreModel(ModelBase):
|
class _KeyValueStoreModel(ModelBase):
|
||||||
@@ -192,7 +194,7 @@ class KeyValueStore:
|
|||||||
return kv.int_value
|
return kv.int_value
|
||||||
|
|
||||||
|
|
||||||
def set_startup_time():
|
def set_startup_time() -> None:
|
||||||
"""
|
"""
|
||||||
sets bot_start_time to the first trade open date - or "now" on new databases.
|
sets bot_start_time to the first trade open date - or "now" on new databases.
|
||||||
sets startup_time to "now"
|
sets startup_time to "now"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from sqlalchemy import inspect, select, text, update
|
from sqlalchemy import Engine, inspect, select, text, update
|
||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.persistence.trade_model import Order, Trade
|
from freqtrade.persistence.trade_model import Order, Trade
|
||||||
@@ -9,7 +9,7 @@ from freqtrade.persistence.trade_model import Order, Trade
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_table_names_for_table(inspector, tabletype) -> list[str]:
|
def get_table_names_for_table(inspector, tabletype: str) -> list[str]:
|
||||||
return [t for t in inspector.get_table_names() if t.startswith(tabletype)]
|
return [t for t in inspector.get_table_names() if t.startswith(tabletype)]
|
||||||
|
|
||||||
|
|
||||||
@@ -350,7 +350,7 @@ def fix_wrong_max_stake_amount(engine):
|
|||||||
connection.execute(stmt)
|
connection.execute(stmt)
|
||||||
|
|
||||||
|
|
||||||
def check_migrate(engine, decl_base, previous_tables) -> None:
|
def check_migrate(engine: Engine, decl_base, previous_tables: list[str]) -> None:
|
||||||
"""
|
"""
|
||||||
Checks if migration is necessary and migrates if necessary
|
Checks if migration is necessary and migrates if necessary
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -96,7 +96,10 @@ def __run_backtest_bg(btconfig: Config):
|
|||||||
)
|
)
|
||||||
|
|
||||||
ApiBG.bt["bt"].results = generate_backtest_stats(
|
ApiBG.bt["bt"].results = generate_backtest_stats(
|
||||||
ApiBG.bt["data"], ApiBG.bt["bt"].all_results, min_date=min_date, max_date=max_date
|
ApiBG.bt["data"],
|
||||||
|
ApiBG.bt["bt"].all_bt_content,
|
||||||
|
min_date=min_date,
|
||||||
|
max_date=max_date,
|
||||||
)
|
)
|
||||||
|
|
||||||
if btconfig.get("export", "none") == "trades":
|
if btconfig.get("export", "none") == "trades":
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ from freqtrade.exceptions import ExchangeError, PricingError
|
|||||||
from freqtrade.exchange import Exchange, timeframe_to_minutes, timeframe_to_msecs
|
from freqtrade.exchange import Exchange, timeframe_to_minutes, timeframe_to_msecs
|
||||||
from freqtrade.exchange.exchange_utils import price_to_precision
|
from freqtrade.exchange.exchange_utils import price_to_precision
|
||||||
from freqtrade.loggers import bufferHandler
|
from freqtrade.loggers import bufferHandler
|
||||||
from freqtrade.persistence import CustomDataWrapper, KeyStoreKeys, KeyValueStore, PairLocks, Trade
|
from freqtrade.persistence import CustomDataWrapper, KeyValueStore, PairLocks, Trade
|
||||||
from freqtrade.persistence.models import PairLock
|
from freqtrade.persistence.models import PairLock
|
||||||
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||||
@@ -635,7 +635,7 @@ class RPC:
|
|||||||
first_date = trades[0].open_date_utc if trades else None
|
first_date = trades[0].open_date_utc if trades else None
|
||||||
last_date = trades[-1].open_date_utc if trades else None
|
last_date = trades[-1].open_date_utc if trades else None
|
||||||
num = float(len(durations) or 1)
|
num = float(len(durations) or 1)
|
||||||
bot_start = KeyValueStore.get_datetime_value(KeyStoreKeys.BOT_START_TIME)
|
bot_start = KeyValueStore.get_datetime_value("bot_start_time")
|
||||||
return {
|
return {
|
||||||
"profit_closed_coin": profit_closed_coin_sum,
|
"profit_closed_coin": profit_closed_coin_sum,
|
||||||
"profit_closed_percent_mean": round(profit_closed_ratio_mean * 100, 2),
|
"profit_closed_percent_mean": round(profit_closed_ratio_mean * 100, 2),
|
||||||
@@ -1601,7 +1601,7 @@ class RPC:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if bot_start := KeyValueStore.get_datetime_value(KeyStoreKeys.BOT_START_TIME):
|
if bot_start := KeyValueStore.get_datetime_value("bot_start_time"):
|
||||||
res.update(
|
res.update(
|
||||||
{
|
{
|
||||||
"bot_start": str(bot_start),
|
"bot_start": str(bot_start),
|
||||||
@@ -1609,7 +1609,7 @@ class RPC:
|
|||||||
"bot_start_ts": int(bot_start.timestamp()),
|
"bot_start_ts": int(bot_start.timestamp()),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if bot_startup := KeyValueStore.get_datetime_value(KeyStoreKeys.STARTUP_TIME):
|
if bot_startup := KeyValueStore.get_datetime_value("startup_time"):
|
||||||
res.update(
|
res.update(
|
||||||
{
|
{
|
||||||
"bot_startup": str(bot_startup),
|
"bot_startup": str(bot_startup),
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ from freqtrade.util.migrations.funding_rate_mig import migrate_funding_fee_timef
|
|||||||
|
|
||||||
|
|
||||||
def migrate_data(config, exchange: Exchange | None = None):
|
def migrate_data(config, exchange: Exchange | None = None):
|
||||||
|
"""
|
||||||
|
Migrate persisted data from old formats to new formats
|
||||||
|
"""
|
||||||
migrate_binance_futures_data(config)
|
migrate_binance_futures_data(config)
|
||||||
|
|
||||||
migrate_funding_fee_timeframe(config, exchange)
|
migrate_funding_fee_timeframe(config, exchange)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ from sqlalchemy import select
|
|||||||
from freqtrade.constants import DOCS_LINK, Config
|
from freqtrade.constants import DOCS_LINK, Config
|
||||||
from freqtrade.enums import TradingMode
|
from freqtrade.enums import TradingMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
|
from freqtrade.persistence import KeyValueStore, Trade
|
||||||
from freqtrade.persistence.pairlock import PairLock
|
from freqtrade.persistence.pairlock import PairLock
|
||||||
from freqtrade.persistence.trade_model import Trade
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -20,6 +20,9 @@ def migrate_binance_futures_names(config: Config):
|
|||||||
):
|
):
|
||||||
# only act on new futures
|
# only act on new futures
|
||||||
return
|
return
|
||||||
|
if KeyValueStore.get_int_value("binance_migration"):
|
||||||
|
# already migrated
|
||||||
|
return
|
||||||
import ccxt
|
import ccxt
|
||||||
|
|
||||||
if version.parse("2.6.26") > version.parse(ccxt.__version__):
|
if version.parse("2.6.26") > version.parse(ccxt.__version__):
|
||||||
@@ -29,10 +32,11 @@ def migrate_binance_futures_names(config: Config):
|
|||||||
)
|
)
|
||||||
_migrate_binance_futures_db(config)
|
_migrate_binance_futures_db(config)
|
||||||
migrate_binance_futures_data(config)
|
migrate_binance_futures_data(config)
|
||||||
|
KeyValueStore.store_value("binance_migration", 1)
|
||||||
|
|
||||||
|
|
||||||
def _migrate_binance_futures_db(config: Config):
|
def _migrate_binance_futures_db(config: Config):
|
||||||
logger.warning("Migrating binance futures pairs in database.")
|
logger.info("Migrating binance futures pairs in database.")
|
||||||
trades = Trade.get_trades([Trade.exchange == "binance", Trade.trading_mode == "FUTURES"]).all()
|
trades = Trade.get_trades([Trade.exchange == "binance", Trade.trading_mode == "FUTURES"]).all()
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
if ":" in trade.pair:
|
if ":" in trade.pair:
|
||||||
@@ -52,7 +56,7 @@ def _migrate_binance_futures_db(config: Config):
|
|||||||
# print(pls)
|
# print(pls)
|
||||||
# pls.update({'pair': concat(PairLock.pair,':USDT')})
|
# pls.update({'pair': concat(PairLock.pair,':USDT')})
|
||||||
Trade.commit()
|
Trade.commit()
|
||||||
logger.warning("Done migrating binance futures pairs in database.")
|
logger.info("Done migrating binance futures pairs in database.")
|
||||||
|
|
||||||
|
|
||||||
def migrate_binance_futures_data(config: Config):
|
def migrate_binance_futures_data(config: Config):
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-r docs/requirements-docs.txt
|
-r docs/requirements-docs.txt
|
||||||
|
|
||||||
coveralls==4.0.1
|
coveralls==4.0.1
|
||||||
ruff==0.11.6
|
ruff==0.11.7
|
||||||
mypy==1.15.0
|
mypy==1.15.0
|
||||||
pre-commit==4.2.0
|
pre-commit==4.2.0
|
||||||
pytest==8.3.5
|
pytest==8.3.5
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
-r requirements-freqai.txt
|
-r requirements-freqai.txt
|
||||||
|
|
||||||
# Required for freqai-rl
|
# Required for freqai-rl
|
||||||
torch==2.6.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
|
torch==2.7.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
|
||||||
gymnasium==0.29.1
|
gymnasium==0.29.1
|
||||||
# SB3 >=2.5.0 depends on torch 2.3.0 - which implies it dropped support x86 macos
|
# SB3 >=2.5.0 depends on torch 2.3.0 - which implies it dropped support x86 macos
|
||||||
stable_baselines3==2.4.1; sys_platform == 'darwin' and platform_machine == 'x86_64'
|
stable_baselines3==2.4.1; sys_platform == 'darwin' and platform_machine == 'x86_64'
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ bottleneck==1.4.2
|
|||||||
numexpr==2.10.2
|
numexpr==2.10.2
|
||||||
pandas-ta==0.3.14b
|
pandas-ta==0.3.14b
|
||||||
|
|
||||||
ccxt==4.4.75
|
ccxt==4.4.77
|
||||||
cryptography==44.0.2
|
cryptography==44.0.2
|
||||||
aiohttp==3.9.5
|
aiohttp==3.9.5
|
||||||
SQLAlchemy==2.0.40
|
SQLAlchemy==2.0.40
|
||||||
@@ -23,7 +23,7 @@ pycoingecko==3.2.0
|
|||||||
jinja2==3.1.6
|
jinja2==3.1.6
|
||||||
joblib==1.4.2
|
joblib==1.4.2
|
||||||
rich==14.0.0
|
rich==14.0.0
|
||||||
pyarrow==19.0.1; platform_machine != 'armv7l'
|
pyarrow==20.0.0; platform_machine != 'armv7l'
|
||||||
|
|
||||||
# find first, C search in arrays
|
# find first, C search in arrays
|
||||||
py_find_1st==1.1.7
|
py_find_1st==1.1.7
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ def test_get_latest_backtest_filename(testdatadir, mocker):
|
|||||||
res = get_latest_backtest_filename(str(testdir_bt))
|
res = get_latest_backtest_filename(str(testdir_bt))
|
||||||
assert res == "backtest-result.json"
|
assert res == "backtest-result.json"
|
||||||
|
|
||||||
mocker.patch("freqtrade.data.btanalysis.json_load", return_value={})
|
mocker.patch("freqtrade.data.btanalysis.bt_fileutils.json_load", return_value={})
|
||||||
|
|
||||||
with pytest.raises(ValueError, match=r"Invalid '.last_result.json' format."):
|
with pytest.raises(ValueError, match=r"Invalid '.last_result.json' format."):
|
||||||
get_latest_backtest_filename(testdir_bt)
|
get_latest_backtest_filename(testdir_bt)
|
||||||
@@ -84,8 +84,8 @@ def test_load_backtest_metadata(mocker, testdatadir):
|
|||||||
res = load_backtest_metadata(testdatadir / "nonexistent.file.json")
|
res = load_backtest_metadata(testdatadir / "nonexistent.file.json")
|
||||||
assert res == {}
|
assert res == {}
|
||||||
|
|
||||||
mocker.patch("freqtrade.data.btanalysis.get_backtest_metadata_filename")
|
mocker.patch("freqtrade.data.btanalysis.bt_fileutils.get_backtest_metadata_filename")
|
||||||
mocker.patch("freqtrade.data.btanalysis.json_load", side_effect=Exception())
|
mocker.patch("freqtrade.data.btanalysis.bt_fileutils.json_load", side_effect=Exception())
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
OperationalException, match=r"Unexpected error.*loading backtest metadata\."
|
OperationalException, match=r"Unexpected error.*loading backtest metadata\."
|
||||||
):
|
):
|
||||||
@@ -94,7 +94,7 @@ def test_load_backtest_metadata(mocker, testdatadir):
|
|||||||
|
|
||||||
def test_load_backtest_data_old_format(testdatadir, mocker):
|
def test_load_backtest_data_old_format(testdatadir, mocker):
|
||||||
filename = testdatadir / "backtest-result_test222.json"
|
filename = testdatadir / "backtest-result_test222.json"
|
||||||
mocker.patch("freqtrade.data.btanalysis.load_backtest_stats", return_value=[])
|
mocker.patch("freqtrade.data.btanalysis.bt_fileutils.load_backtest_stats", return_value=[])
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
OperationalException,
|
OperationalException,
|
||||||
@@ -149,7 +149,7 @@ def test_load_backtest_data_multi(testdatadir):
|
|||||||
def test_load_trades_from_db(default_conf, fee, is_short, mocker):
|
def test_load_trades_from_db(default_conf, fee, is_short, mocker):
|
||||||
create_mock_trades(fee, is_short)
|
create_mock_trades(fee, is_short)
|
||||||
# remove init so it does not init again
|
# remove init so it does not init again
|
||||||
init_mock = mocker.patch("freqtrade.data.btanalysis.init_db", MagicMock())
|
init_mock = mocker.patch("freqtrade.data.btanalysis.bt_fileutils.init_db", MagicMock())
|
||||||
|
|
||||||
trades = load_trades_from_db(db_url=default_conf["db_url"])
|
trades = load_trades_from_db(db_url=default_conf["db_url"])
|
||||||
assert init_mock.call_count == 1
|
assert init_mock.call_count == 1
|
||||||
@@ -221,8 +221,10 @@ def test_analyze_trade_parallelism(testdatadir):
|
|||||||
|
|
||||||
|
|
||||||
def test_load_trades(default_conf, mocker):
|
def test_load_trades(default_conf, mocker):
|
||||||
db_mock = mocker.patch("freqtrade.data.btanalysis.load_trades_from_db", MagicMock())
|
db_mock = mocker.patch(
|
||||||
bt_mock = mocker.patch("freqtrade.data.btanalysis.load_backtest_data", MagicMock())
|
"freqtrade.data.btanalysis.bt_fileutils.load_trades_from_db", MagicMock()
|
||||||
|
)
|
||||||
|
bt_mock = mocker.patch("freqtrade.data.btanalysis.bt_fileutils.load_backtest_data", MagicMock())
|
||||||
|
|
||||||
load_trades(
|
load_trades(
|
||||||
"DB",
|
"DB",
|
||||||
|
|||||||
@@ -408,13 +408,14 @@ EXCHANGES = {
|
|||||||
"candle_count": 200,
|
"candle_count": 200,
|
||||||
"orderbook_max_entries": 50,
|
"orderbook_max_entries": 50,
|
||||||
},
|
},
|
||||||
"htx": {
|
# TODO: verify why htx is not working in CI.
|
||||||
"pair": "ETH/BTC",
|
# "htx": {
|
||||||
"stake_currency": "BTC",
|
# "pair": "ETH/BTC",
|
||||||
"hasQuoteVolume": True,
|
# "stake_currency": "BTC",
|
||||||
"timeframe": "1h",
|
# "hasQuoteVolume": True,
|
||||||
"candle_count": 1000,
|
# "timeframe": "1h",
|
||||||
},
|
# "candle_count": 1000,
|
||||||
|
# },
|
||||||
"bitvavo": {
|
"bitvavo": {
|
||||||
"pair": "BTC/EUR",
|
"pair": "BTC/EUR",
|
||||||
"stake_currency": "EUR",
|
"stake_currency": "EUR",
|
||||||
|
|||||||
@@ -2576,7 +2576,7 @@ def test_backtest_start_multi_strat_caching(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
"freqtrade.data.btanalysis",
|
"freqtrade.data.btanalysis.bt_fileutils",
|
||||||
load_backtest_metadata=load_backtest_metadata,
|
load_backtest_metadata=load_backtest_metadata,
|
||||||
load_backtest_stats=load_backtest_stats,
|
load_backtest_stats=load_backtest_stats,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2869,7 +2869,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmp_path):
|
|||||||
def test_api_backtest_history(botclient, mocker, testdatadir):
|
def test_api_backtest_history(botclient, mocker, testdatadir):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
"freqtrade.data.btanalysis._get_backtest_files",
|
"freqtrade.data.btanalysis.bt_fileutils._get_backtest_files",
|
||||||
return_value=[
|
return_value=[
|
||||||
testdatadir / "backtest_results/backtest-result_multistrat.json",
|
testdatadir / "backtest_results/backtest-result_multistrat.json",
|
||||||
testdatadir / "backtest_results/backtest-result.json",
|
testdatadir / "backtest_results/backtest-result.json",
|
||||||
|
|||||||
Reference in New Issue
Block a user