Merge pull request #10746 from xzmeng/startup-time

Postpone imports on demand
This commit is contained in:
Matthias
2024-10-06 08:21:54 +02:00
committed by GitHub
25 changed files with 498 additions and 406 deletions

View File

@@ -2,7 +2,6 @@ import logging
from pathlib import Path from pathlib import Path
from typing import Any, Dict from typing import Any, Dict
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.exceptions import ConfigurationError, OperationalException from freqtrade.exceptions import ConfigurationError, OperationalException
@@ -17,6 +16,8 @@ def setup_analyze_configuration(args: Dict[str, Any], method: RunMode) -> Dict[s
:param method: Bot running mode :param method: Bot running mode
:return: Configuration :return: Configuration
""" """
from freqtrade.configuration import setup_utils_configuration
config = setup_utils_configuration(args, method) config = setup_utils_configuration(args, method)
no_unlimited_runmodes = { no_unlimited_runmodes = {

View File

@@ -1,261 +1,27 @@
import logging import logging
import secrets
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List from typing import Any, Dict
from questionary import Separator, prompt
from freqtrade.configuration import sanitize_config
from freqtrade.configuration.config_setup import setup_utils_configuration
from freqtrade.configuration.detect_environment import running_in_docker
from freqtrade.configuration.directory_operations import chown_user_directory
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS, available_exchanges
from freqtrade.util import render_template
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def validate_is_int(val):
try:
_ = int(val)
return True
except Exception:
return False
def validate_is_float(val):
try:
_ = float(val)
return True
except Exception:
return False
def ask_user_overwrite(config_path: Path) -> bool:
questions = [
{
"type": "confirm",
"name": "overwrite",
"message": f"File {config_path} already exists. Overwrite?",
"default": False,
},
]
answers = prompt(questions)
return answers["overwrite"]
def ask_user_config() -> Dict[str, Any]:
"""
Ask user a few questions to build the configuration.
Interactive questions built using https://github.com/tmbo/questionary
:returns: Dict with keys to put into template
"""
questions: List[Dict[str, Any]] = [
{
"type": "confirm",
"name": "dry_run",
"message": "Do you want to enable Dry-run (simulated trades)?",
"default": True,
},
{
"type": "text",
"name": "stake_currency",
"message": "Please insert your stake currency:",
"default": "USDT",
},
{
"type": "text",
"name": "stake_amount",
"message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):",
"default": "unlimited",
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
"filter": lambda val: (
'"' + UNLIMITED_STAKE_AMOUNT + '"' if val == UNLIMITED_STAKE_AMOUNT else val
),
},
{
"type": "text",
"name": "max_open_trades",
"message": "Please insert max_open_trades (Integer or -1 for unlimited open trades):",
"default": "3",
"validate": lambda val: validate_is_int(val),
},
{
"type": "select",
"name": "timeframe_in_config",
"message": "Time",
"choices": ["Have the strategy define timeframe.", "Override in configuration."],
},
{
"type": "text",
"name": "timeframe",
"message": "Please insert your desired timeframe (e.g. 5m):",
"default": "5m",
"when": lambda x: x["timeframe_in_config"] == "Override in configuration.",
},
{
"type": "text",
"name": "fiat_display_currency",
"message": (
"Please insert your display Currency for reporting "
"(leave empty to disable FIAT conversion):"
),
"default": "USD",
},
{
"type": "select",
"name": "exchange_name",
"message": "Select exchange",
"choices": [
"binance",
"binanceus",
"bingx",
"gate",
"htx",
"kraken",
"kucoin",
"okx",
Separator("------------------"),
"other",
],
},
{
"type": "confirm",
"name": "trading_mode",
"message": "Do you want to trade Perpetual Swaps (perpetual futures)?",
"default": False,
"filter": lambda val: "futures" if val else "spot",
"when": lambda x: x["exchange_name"] in ["binance", "gate", "okx", "bybit"],
},
{
"type": "autocomplete",
"name": "exchange_name",
"message": "Type your exchange name (Must be supported by ccxt)",
"choices": available_exchanges(),
"when": lambda x: x["exchange_name"] == "other",
},
{
"type": "password",
"name": "exchange_key",
"message": "Insert Exchange Key",
"when": lambda x: not x["dry_run"],
},
{
"type": "password",
"name": "exchange_secret",
"message": "Insert Exchange Secret",
"when": lambda x: not x["dry_run"],
},
{
"type": "password",
"name": "exchange_key_password",
"message": "Insert Exchange API Key password",
"when": lambda x: not x["dry_run"] and x["exchange_name"] in ("kucoin", "okx"),
},
{
"type": "confirm",
"name": "telegram",
"message": "Do you want to enable Telegram?",
"default": False,
},
{
"type": "password",
"name": "telegram_token",
"message": "Insert Telegram token",
"when": lambda x: x["telegram"],
},
{
"type": "password",
"name": "telegram_chat_id",
"message": "Insert Telegram chat id",
"when": lambda x: x["telegram"],
},
{
"type": "confirm",
"name": "api_server",
"message": "Do you want to enable the Rest API (includes FreqUI)?",
"default": False,
},
{
"type": "text",
"name": "api_server_listen_addr",
"message": (
"Insert Api server Listen Address (0.0.0.0 for docker, "
"otherwise best left untouched)"
),
"default": "127.0.0.1" if not running_in_docker() else "0.0.0.0", # noqa: S104
"when": lambda x: x["api_server"],
},
{
"type": "text",
"name": "api_server_username",
"message": "Insert api-server username",
"default": "freqtrader",
"when": lambda x: x["api_server"],
},
{
"type": "password",
"name": "api_server_password",
"message": "Insert api-server password",
"when": lambda x: x["api_server"],
},
]
answers = prompt(questions)
if not answers:
# Interrupted questionary sessions return an empty dict.
raise OperationalException("User interrupted interactive questions.")
# Ensure default is set for non-futures exchanges
answers["trading_mode"] = answers.get("trading_mode", "spot")
answers["margin_mode"] = "isolated" if answers.get("trading_mode") == "futures" else ""
# Force JWT token to be a random string
answers["api_server_jwt_key"] = secrets.token_hex()
answers["api_server_ws_token"] = secrets.token_urlsafe(25)
return answers
def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
"""
Applies selections to the template and writes the result to config_path
:param config_path: Path object for new config file. Should not exist yet
:param selections: Dict containing selections taken by the user.
"""
from jinja2.exceptions import TemplateNotFound
try:
exchange_template = MAP_EXCHANGE_CHILDCLASS.get(
selections["exchange_name"], selections["exchange_name"]
)
selections["exchange"] = render_template(
templatefile=f"subtemplates/exchange_{exchange_template}.j2", arguments=selections
)
except TemplateNotFound:
selections["exchange"] = render_template(
templatefile="subtemplates/exchange_generic.j2", arguments=selections
)
config_text = render_template(templatefile="base_config.json.j2", arguments=selections)
logger.info(f"Writing config to `{config_path}`.")
logger.info(
"Please make sure to check the configuration contents and adjust settings to your needs."
)
config_path.write_text(config_text)
def start_new_config(args: Dict[str, Any]) -> None: def start_new_config(args: Dict[str, Any]) -> None:
""" """
Create a new strategy from a template Create a new strategy from a template
Asking the user questions to fill out the template accordingly. Asking the user questions to fill out the template accordingly.
""" """
from freqtrade.configuration.deploy_config import (
ask_user_config,
ask_user_overwrite,
deploy_new_config,
)
from freqtrade.configuration.directory_operations import chown_user_directory
config_path = Path(args["config"][0]) config_path = Path(args["config"][0])
chown_user_directory(config_path.parent) chown_user_directory(config_path.parent)
if config_path.exists(): if config_path.exists():
@@ -272,6 +38,9 @@ def start_new_config(args: Dict[str, Any]) -> None:
def start_show_config(args: Dict[str, Any]) -> None: def start_show_config(args: Dict[str, Any]) -> None:
from freqtrade.configuration import sanitize_config
from freqtrade.configuration.config_setup import setup_utils_configuration
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE, set_dry=False) config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE, set_dry=False)
print("Your combined configuration is:") print("Your combined configuration is:")

View File

@@ -3,22 +3,10 @@ import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, Dict from typing import Any, Dict
from freqtrade.configuration import TimeRange, setup_utils_configuration
from freqtrade.constants import DATETIME_PRINT_FORMAT, DL_DATA_TIMEFRAMES, Config from freqtrade.constants import DATETIME_PRINT_FORMAT, DL_DATA_TIMEFRAMES, Config
from freqtrade.data.converter import (
convert_ohlcv_format,
convert_trades_format,
convert_trades_to_ohlcv,
)
from freqtrade.data.history import download_data_main
from freqtrade.enums import CandleType, RunMode, TradingMode from freqtrade.enums import CandleType, RunMode, TradingMode
from freqtrade.exceptions import ConfigurationError from freqtrade.exceptions import ConfigurationError
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import plural
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
from freqtrade.resolvers import ExchangeResolver
from freqtrade.util import print_rich_table
from freqtrade.util.migrations import migrate_data
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -42,6 +30,9 @@ def start_download_data(args: Dict[str, Any]) -> None:
""" """
Download data (former download_backtest_data.py script) Download data (former download_backtest_data.py script)
""" """
from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.history import download_data_main
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
_check_data_config_download_sanity(config) _check_data_config_download_sanity(config)
@@ -54,6 +45,10 @@ def start_download_data(args: Dict[str, Any]) -> None:
def start_convert_trades(args: Dict[str, Any]) -> None: def start_convert_trades(args: Dict[str, Any]) -> None:
from freqtrade.configuration import TimeRange, setup_utils_configuration
from freqtrade.data.converter import convert_trades_to_ohlcv
from freqtrade.resolvers import ExchangeResolver
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
timerange = TimeRange() timerange = TimeRange()
@@ -96,6 +91,10 @@ def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
""" """
Convert data from one format to another Convert data from one format to another
""" """
from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.converter import convert_ohlcv_format, convert_trades_format
from freqtrade.util.migrations import migrate_data
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
if ohlcv: if ohlcv:
migrate_data(config) migrate_data(config)
@@ -118,6 +117,9 @@ def start_list_data(args: Dict[str, Any]) -> None:
""" """
List available OHLCV data List available OHLCV data
""" """
from freqtrade.configuration import setup_utils_configuration
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.util import print_rich_table
if args["trades"]: if args["trades"]:
start_list_trades_data(args) start_list_trades_data(args)
@@ -181,6 +183,9 @@ def start_list_trades_data(args: Dict[str, Any]) -> None:
""" """
List available Trades data List available Trades data
""" """
from freqtrade.configuration import setup_utils_configuration
from freqtrade.misc import plural
from freqtrade.util import print_rich_table
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)

View File

@@ -1,9 +1,6 @@
import logging import logging
from typing import Any, Dict from typing import Any, Dict
from sqlalchemy import func, select
from freqtrade.configuration.config_setup import setup_utils_configuration
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
@@ -11,8 +8,10 @@ logger = logging.getLogger(__name__)
def start_convert_db(args: Dict[str, Any]) -> None: def start_convert_db(args: Dict[str, Any]) -> None:
from sqlalchemy import func, select
from sqlalchemy.orm import make_transient from sqlalchemy.orm import make_transient
from freqtrade.configuration.config_setup import setup_utils_configuration
from freqtrade.persistence import Order, Trade, init_db from freqtrade.persistence import Order, Trade, init_db
from freqtrade.persistence.migrations import set_sequence_ids from freqtrade.persistence.migrations import set_sequence_ids
from freqtrade.persistence.pairlock import PairLock from freqtrade.persistence.pairlock import PairLock

View File

@@ -1,16 +1,11 @@
import logging import logging
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Optional, Tuple from typing import Any, Dict
import requests
from freqtrade.configuration import setup_utils_configuration
from freqtrade.configuration.directory_operations import copy_sample_files, create_userdata_dir
from freqtrade.constants import USERPATH_STRATEGIES from freqtrade.constants import USERPATH_STRATEGIES
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.exceptions import ConfigurationError, OperationalException from freqtrade.exceptions import ConfigurationError, OperationalException
from freqtrade.util import render_template, render_template_with_fallback
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -26,6 +21,8 @@ def start_create_userdir(args: Dict[str, Any]) -> None:
:param args: Cli args from Arguments() :param args: Cli args from Arguments()
:return: None :return: None
""" """
from freqtrade.configuration.directory_operations import copy_sample_files, create_userdata_dir
if "user_data_dir" in args and args["user_data_dir"]: if "user_data_dir" in args and args["user_data_dir"]:
userdir = create_userdata_dir(args["user_data_dir"], create_dir=True) userdir = create_userdata_dir(args["user_data_dir"], create_dir=True)
copy_sample_files(userdir, overwrite=args["reset"]) copy_sample_files(userdir, overwrite=args["reset"])
@@ -38,6 +35,8 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st
""" """
Deploy new strategy from template to strategy_path Deploy new strategy from template to strategy_path
""" """
from freqtrade.util import render_template, render_template_with_fallback
fallback = "full" fallback = "full"
attributes = render_template_with_fallback( attributes = render_template_with_fallback(
templatefile=f"strategy_subtemplates/strategy_attributes_{subtemplate}.j2", templatefile=f"strategy_subtemplates/strategy_attributes_{subtemplate}.j2",
@@ -82,6 +81,8 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st
def start_new_strategy(args: Dict[str, Any]) -> None: def start_new_strategy(args: Dict[str, Any]) -> None:
from freqtrade.configuration import setup_utils_configuration
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
if "strategy" in args and args["strategy"]: if "strategy" in args and args["strategy"]:
@@ -98,80 +99,14 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
raise ConfigurationError("`new-strategy` requires --strategy to be set.") raise ConfigurationError("`new-strategy` requires --strategy to be set.")
def clean_ui_subdir(directory: Path):
if directory.is_dir():
logger.info("Removing UI directory content.")
for p in reversed(list(directory.glob("**/*"))): # iterate contents from leaves to root
if p.name in (".gitkeep", "fallback_file.html"):
continue
if p.is_file():
p.unlink()
elif p.is_dir():
p.rmdir()
def read_ui_version(dest_folder: Path) -> Optional[str]:
file = dest_folder / ".uiversion"
if not file.is_file():
return None
with file.open("r") as f:
return f.read()
def download_and_install_ui(dest_folder: Path, dl_url: str, version: str):
from io import BytesIO
from zipfile import ZipFile
logger.info(f"Downloading {dl_url}")
resp = requests.get(dl_url, timeout=req_timeout).content
dest_folder.mkdir(parents=True, exist_ok=True)
with ZipFile(BytesIO(resp)) as zf:
for fn in zf.filelist:
with zf.open(fn) as x:
destfile = dest_folder / fn.filename
if fn.is_dir():
destfile.mkdir(exist_ok=True)
else:
destfile.write_bytes(x.read())
with (dest_folder / ".uiversion").open("w") as f:
f.write(version)
def get_ui_download_url(version: Optional[str] = None) -> Tuple[str, str]:
base_url = "https://api.github.com/repos/freqtrade/frequi/"
# Get base UI Repo path
resp = requests.get(f"{base_url}releases", timeout=req_timeout)
resp.raise_for_status()
r = resp.json()
if version:
tmp = [x for x in r if x["name"] == version]
if tmp:
latest_version = tmp[0]["name"]
assets = tmp[0].get("assets", [])
else:
raise ValueError("UI-Version not found.")
else:
latest_version = r[0]["name"]
assets = r[0].get("assets", [])
dl_url = ""
if assets and len(assets) > 0:
dl_url = assets[0]["browser_download_url"]
# URL not found - try assets url
if not dl_url:
assets = r[0]["assets_url"]
resp = requests.get(assets, timeout=req_timeout)
r = resp.json()
dl_url = r[0]["browser_download_url"]
return dl_url, latest_version
def start_install_ui(args: Dict[str, Any]) -> None: def start_install_ui(args: Dict[str, Any]) -> None:
from freqtrade.commands.deploy_ui import (
clean_ui_subdir,
download_and_install_ui,
get_ui_download_url,
read_ui_version,
)
dest_folder = Path(__file__).parents[1] / "rpc/api_server/ui/installed/" dest_folder = Path(__file__).parents[1] / "rpc/api_server/ui/installed/"
# First make sure the assets are removed. # First make sure the assets are removed.
dl_url, latest_version = get_ui_download_url(args.get("ui_version")) dl_url, latest_version = get_ui_download_url(args.get("ui_version"))

View File

@@ -0,0 +1,84 @@
import logging
from pathlib import Path
from typing import Optional, Tuple
import requests
logger = logging.getLogger(__name__)
# Timeout for requests
req_timeout = 30
def clean_ui_subdir(directory: Path):
if directory.is_dir():
logger.info("Removing UI directory content.")
for p in reversed(list(directory.glob("**/*"))): # iterate contents from leaves to root
if p.name in (".gitkeep", "fallback_file.html"):
continue
if p.is_file():
p.unlink()
elif p.is_dir():
p.rmdir()
def read_ui_version(dest_folder: Path) -> Optional[str]:
file = dest_folder / ".uiversion"
if not file.is_file():
return None
with file.open("r") as f:
return f.read()
def download_and_install_ui(dest_folder: Path, dl_url: str, version: str):
from io import BytesIO
from zipfile import ZipFile
logger.info(f"Downloading {dl_url}")
resp = requests.get(dl_url, timeout=req_timeout).content
dest_folder.mkdir(parents=True, exist_ok=True)
with ZipFile(BytesIO(resp)) as zf:
for fn in zf.filelist:
with zf.open(fn) as x:
destfile = dest_folder / fn.filename
if fn.is_dir():
destfile.mkdir(exist_ok=True)
else:
destfile.write_bytes(x.read())
with (dest_folder / ".uiversion").open("w") as f:
f.write(version)
def get_ui_download_url(version: Optional[str] = None) -> Tuple[str, str]:
base_url = "https://api.github.com/repos/freqtrade/frequi/"
# Get base UI Repo path
resp = requests.get(f"{base_url}releases", timeout=req_timeout)
resp.raise_for_status()
r = resp.json()
if version:
tmp = [x for x in r if x["name"] == version]
if tmp:
latest_version = tmp[0]["name"]
assets = tmp[0].get("assets", [])
else:
raise ValueError("UI-Version not found.")
else:
latest_version = r[0]["name"]
assets = r[0].get("assets", [])
dl_url = ""
if assets and len(assets) > 0:
dl_url = assets[0]["browser_download_url"]
# URL not found - try assets url
if not dl_url:
assets = r[0]["assets_url"]
resp = requests.get(assets, timeout=req_timeout)
r = resp.json()
dl_url = r[0]["browser_download_url"]
return dl_url, latest_version

View File

@@ -2,11 +2,8 @@ import logging
from operator import itemgetter from operator import itemgetter
from typing import Any, Dict from typing import Any, Dict
from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.btanalysis import get_latest_hyperopt_file
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.optimize.optimize_reports import show_backtest_result
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -16,6 +13,8 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
""" """
List hyperopt epochs previously evaluated List hyperopt epochs previously evaluated
""" """
from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.btanalysis import get_latest_hyperopt_file
from freqtrade.optimize.hyperopt_output import HyperoptOutput from freqtrade.optimize.hyperopt_output import HyperoptOutput
from freqtrade.optimize.hyperopt_tools import HyperoptTools from freqtrade.optimize.hyperopt_tools import HyperoptTools
@@ -61,7 +60,10 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
""" """
Show details of a hyperopt epoch previously evaluated Show details of a hyperopt epoch previously evaluated
""" """
from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.btanalysis import get_latest_hyperopt_file
from freqtrade.optimize.hyperopt_tools import HyperoptTools from freqtrade.optimize.hyperopt_tools import HyperoptTools
from freqtrade.optimize.optimize_reports import show_backtest_result
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)

View File

@@ -3,19 +3,9 @@ import logging
import sys import sys
from typing import Any, Dict, List, Union from typing import Any, Dict, List, Union
import rapidjson
from rich.console import Console
from rich.table import Table
from rich.text import Text
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.exceptions import ConfigurationError, OperationalException from freqtrade.exceptions import ConfigurationError, OperationalException
from freqtrade.exchange import list_available_exchanges, market_is_active
from freqtrade.ft_types import ValidExchangesType from freqtrade.ft_types import ValidExchangesType
from freqtrade.misc import parse_db_uri_for_logging, plural
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.util import print_rich_table
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -27,6 +17,12 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
:param args: Cli args from Arguments() :param args: Cli args from Arguments()
:return: None :return: None
""" """
from rich.console import Console
from rich.table import Table
from rich.text import Text
from freqtrade.exchange import list_available_exchanges
available_exchanges: List[ValidExchangesType] = list_available_exchanges( available_exchanges: List[ValidExchangesType] = list_available_exchanges(
args["list_exchanges_all"] args["list_exchanges_all"]
) )
@@ -86,6 +82,10 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
def _print_objs_tabular(objs: List, print_colorized: bool) -> None: def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
from rich.console import Console
from rich.table import Table
from rich.text import Text
names = [s["name"] for s in objs] names = [s["name"] for s in objs]
objs_to_print: List[Dict[str, Union[Text, str]]] = [ objs_to_print: List[Dict[str, Union[Text, str]]] = [
{ {
@@ -129,6 +129,9 @@ def start_list_strategies(args: Dict[str, Any]) -> None:
""" """
Print files with Strategy custom classes available in the directory Print files with Strategy custom classes available in the directory
""" """
from freqtrade.configuration import setup_utils_configuration
from freqtrade.resolvers import StrategyResolver
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
strategy_objs = StrategyResolver.search_all_objects( strategy_objs = StrategyResolver.search_all_objects(
@@ -152,9 +155,11 @@ def start_list_freqAI_models(args: Dict[str, Any]) -> None:
""" """
Print files with FreqAI models custom classes available in the directory Print files with FreqAI models custom classes available in the directory
""" """
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) from freqtrade.configuration import setup_utils_configuration
from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
model_objs = FreqaiModelResolver.search_all_objects(config, not args["print_one_column"]) model_objs = FreqaiModelResolver.search_all_objects(config, not args["print_one_column"])
# Sort alphabetically # Sort alphabetically
model_objs = sorted(model_objs, key=lambda x: x["name"]) model_objs = sorted(model_objs, key=lambda x: x["name"])
@@ -168,6 +173,9 @@ def start_list_timeframes(args: Dict[str, Any]) -> None:
""" """
Print timeframes available on Exchange Print timeframes available on Exchange
""" """
from freqtrade.configuration import setup_utils_configuration
from freqtrade.resolvers import ExchangeResolver
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
# Do not use timeframe set in the config # Do not use timeframe set in the config
config["timeframe"] = None config["timeframe"] = None
@@ -191,6 +199,12 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
:param pairs_only: if True print only pairs, otherwise print all instruments (markets) :param pairs_only: if True print only pairs, otherwise print all instruments (markets)
:return: None :return: None
""" """
from freqtrade.configuration import setup_utils_configuration
from freqtrade.exchange import market_is_active
from freqtrade.misc import plural
from freqtrade.resolvers import ExchangeResolver
from freqtrade.util import print_rich_table
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
# Init exchange # Init exchange
@@ -281,6 +295,8 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
elif args.get("print_one_column", False): elif args.get("print_one_column", False):
print("\n".join(pairs.keys())) print("\n".join(pairs.keys()))
elif args.get("list_pairs_print_json", False): elif args.get("list_pairs_print_json", False):
import rapidjson
print(rapidjson.dumps(list(pairs.keys()), default=str)) print(rapidjson.dumps(list(pairs.keys()), default=str))
elif args.get("print_csv", False): elif args.get("print_csv", False):
writer = csv.DictWriter(sys.stdout, fieldnames=headers) writer = csv.DictWriter(sys.stdout, fieldnames=headers)
@@ -302,6 +318,8 @@ def start_show_trades(args: Dict[str, Any]) -> None:
""" """
import json import json
from freqtrade.configuration import setup_utils_configuration
from freqtrade.misc import parse_db_uri_for_logging
from freqtrade.persistence import Trade, init_db from freqtrade.persistence import Trade, init_db
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)

View File

@@ -2,10 +2,8 @@ import logging
from typing import Any, Dict from typing import Any, Dict
from freqtrade import constants from freqtrade import constants
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.exceptions import ConfigurationError, OperationalException from freqtrade.exceptions import ConfigurationError, OperationalException
from freqtrade.util import fmt_coin
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -18,6 +16,9 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[
:param method: Bot running mode :param method: Bot running mode
:return: Configuration :return: Configuration
""" """
from freqtrade.configuration import setup_utils_configuration
from freqtrade.util import fmt_coin
config = setup_utils_configuration(args, method) config = setup_utils_configuration(args, method)
no_unlimited_runmodes = { no_unlimited_runmodes = {
@@ -64,6 +65,7 @@ def start_backtesting_show(args: Dict[str, Any]) -> None:
""" """
Show previous backtest result Show previous backtest result
""" """
from freqtrade.configuration import setup_utils_configuration
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
@@ -144,6 +146,7 @@ def start_lookahead_analysis(args: Dict[str, Any]) -> None:
:param args: Cli args from Arguments() :param args: Cli args from Arguments()
:return: None :return: None
""" """
from freqtrade.configuration import setup_utils_configuration
from freqtrade.optimize.analysis.lookahead_helpers import LookaheadAnalysisSubFunctions from freqtrade.optimize.analysis.lookahead_helpers import LookaheadAnalysisSubFunctions
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
@@ -156,6 +159,7 @@ def start_recursive_analysis(args: Dict[str, Any]) -> None:
:param args: Cli args from Arguments() :param args: Cli args from Arguments()
:return: None :return: None
""" """
from freqtrade.configuration import setup_utils_configuration
from freqtrade.optimize.analysis.recursive_helpers import RecursiveAnalysisSubFunctions from freqtrade.optimize.analysis.recursive_helpers import RecursiveAnalysisSubFunctions
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)

View File

@@ -3,9 +3,7 @@ from typing import Any, Dict
import rapidjson import rapidjson
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.resolvers import ExchangeResolver
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -15,8 +13,10 @@ def start_test_pairlist(args: Dict[str, Any]) -> None:
""" """
Test Pairlist configuration Test Pairlist configuration
""" """
from freqtrade.configuration import setup_utils_configuration
from freqtrade.persistence import FtNoDBContext from freqtrade.persistence import FtNoDBContext
from freqtrade.plugins.pairlistmanager import PairListManager from freqtrade.plugins.pairlistmanager import PairListManager
from freqtrade.resolvers import ExchangeResolver
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)

View File

@@ -1,6 +1,5 @@
from typing import Any, Dict from typing import Any, Dict
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.exceptions import ConfigurationError from freqtrade.exceptions import ConfigurationError
@@ -18,6 +17,7 @@ def start_plot_dataframe(args: Dict[str, Any]) -> None:
Entrypoint for dataframe plotting Entrypoint for dataframe plotting
""" """
# Import here to avoid errors if plot-dependencies are not installed. # Import here to avoid errors if plot-dependencies are not installed.
from freqtrade.configuration import setup_utils_configuration
from freqtrade.plot.plotting import load_and_plot_trades from freqtrade.plot.plotting import load_and_plot_trades
validate_plot_args(args) validate_plot_args(args)
@@ -31,6 +31,7 @@ def start_plot_profit(args: Dict[str, Any]) -> None:
Entrypoint for plot_profit Entrypoint for plot_profit
""" """
# Import here to avoid errors if plot-dependencies are not installed. # Import here to avoid errors if plot-dependencies are not installed.
from freqtrade.configuration import setup_utils_configuration
from freqtrade.plot.plotting import plot_profit from freqtrade.plot.plotting import plot_profit
validate_plot_args(args) validate_plot_args(args)

View File

@@ -3,10 +3,7 @@ import time
from pathlib import Path from pathlib import Path
from typing import Any, Dict from typing import Any, Dict
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.strategyupdater import StrategyUpdater
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -18,6 +15,8 @@ def start_strategy_update(args: Dict[str, Any]) -> None:
:param args: Cli args from Arguments() :param args: Cli args from Arguments()
:return: None :return: None
""" """
from freqtrade.configuration import setup_utils_configuration
from freqtrade.resolvers import StrategyResolver
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
@@ -45,6 +44,8 @@ def start_strategy_update(args: Dict[str, Any]) -> None:
def start_conversion(strategy_obj, config): def start_conversion(strategy_obj, config):
from freqtrade.strategy.strategyupdater import StrategyUpdater
print(f"Conversion of {Path(strategy_obj['location']).name} started.") print(f"Conversion of {Path(strategy_obj['location']).name} started.")
instance_strategy_updater = StrategyUpdater() instance_strategy_updater = StrategyUpdater()
start = time.perf_counter() start = time.perf_counter()

View File

@@ -1,6 +1,5 @@
# flake8: noqa: F401 # flake8: noqa: F401
from freqtrade.configuration.asyncio_config import asyncio_setup
from freqtrade.configuration.config_secrets import sanitize_config from freqtrade.configuration.config_secrets import sanitize_config
from freqtrade.configuration.config_setup import setup_utils_configuration from freqtrade.configuration.config_setup import setup_utils_configuration
from freqtrade.configuration.config_validation import validate_config_consistency from freqtrade.configuration.config_validation import validate_config_consistency

View File

@@ -0,0 +1,250 @@
import logging
import secrets
from pathlib import Path
from typing import Any, Dict, List
from questionary import Separator, prompt
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)
def validate_is_int(val):
try:
_ = int(val)
return True
except Exception:
return False
def validate_is_float(val):
try:
_ = float(val)
return True
except Exception:
return False
def ask_user_overwrite(config_path: Path) -> bool:
questions = [
{
"type": "confirm",
"name": "overwrite",
"message": f"File {config_path} already exists. Overwrite?",
"default": False,
},
]
answers = prompt(questions)
return answers["overwrite"]
def ask_user_config() -> Dict[str, Any]:
"""
Ask user a few questions to build the configuration.
Interactive questions built using https://github.com/tmbo/questionary
:returns: Dict with keys to put into template
"""
from freqtrade.configuration.detect_environment import running_in_docker
from freqtrade.exchange import available_exchanges
questions: List[Dict[str, Any]] = [
{
"type": "confirm",
"name": "dry_run",
"message": "Do you want to enable Dry-run (simulated trades)?",
"default": True,
},
{
"type": "text",
"name": "stake_currency",
"message": "Please insert your stake currency:",
"default": "USDT",
},
{
"type": "text",
"name": "stake_amount",
"message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):",
"default": "unlimited",
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
"filter": lambda val: (
'"' + UNLIMITED_STAKE_AMOUNT + '"' if val == UNLIMITED_STAKE_AMOUNT else val
),
},
{
"type": "text",
"name": "max_open_trades",
"message": "Please insert max_open_trades (Integer or -1 for unlimited open trades):",
"default": "3",
"validate": lambda val: validate_is_int(val),
},
{
"type": "select",
"name": "timeframe_in_config",
"message": "Time",
"choices": ["Have the strategy define timeframe.", "Override in configuration."],
},
{
"type": "text",
"name": "timeframe",
"message": "Please insert your desired timeframe (e.g. 5m):",
"default": "5m",
"when": lambda x: x["timeframe_in_config"] == "Override in configuration.",
},
{
"type": "text",
"name": "fiat_display_currency",
"message": (
"Please insert your display Currency for reporting "
"(leave empty to disable FIAT conversion):"
),
"default": "USD",
},
{
"type": "select",
"name": "exchange_name",
"message": "Select exchange",
"choices": [
"binance",
"binanceus",
"bingx",
"gate",
"htx",
"kraken",
"kucoin",
"okx",
Separator("------------------"),
"other",
],
},
{
"type": "confirm",
"name": "trading_mode",
"message": "Do you want to trade Perpetual Swaps (perpetual futures)?",
"default": False,
"filter": lambda val: "futures" if val else "spot",
"when": lambda x: x["exchange_name"] in ["binance", "gate", "okx", "bybit"],
},
{
"type": "autocomplete",
"name": "exchange_name",
"message": "Type your exchange name (Must be supported by ccxt)",
"choices": available_exchanges(),
"when": lambda x: x["exchange_name"] == "other",
},
{
"type": "password",
"name": "exchange_key",
"message": "Insert Exchange Key",
"when": lambda x: not x["dry_run"],
},
{
"type": "password",
"name": "exchange_secret",
"message": "Insert Exchange Secret",
"when": lambda x: not x["dry_run"],
},
{
"type": "password",
"name": "exchange_key_password",
"message": "Insert Exchange API Key password",
"when": lambda x: not x["dry_run"] and x["exchange_name"] in ("kucoin", "okx"),
},
{
"type": "confirm",
"name": "telegram",
"message": "Do you want to enable Telegram?",
"default": False,
},
{
"type": "password",
"name": "telegram_token",
"message": "Insert Telegram token",
"when": lambda x: x["telegram"],
},
{
"type": "password",
"name": "telegram_chat_id",
"message": "Insert Telegram chat id",
"when": lambda x: x["telegram"],
},
{
"type": "confirm",
"name": "api_server",
"message": "Do you want to enable the Rest API (includes FreqUI)?",
"default": False,
},
{
"type": "text",
"name": "api_server_listen_addr",
"message": (
"Insert Api server Listen Address (0.0.0.0 for docker, "
"otherwise best left untouched)"
),
"default": "127.0.0.1" if not running_in_docker() else "0.0.0.0", # noqa: S104
"when": lambda x: x["api_server"],
},
{
"type": "text",
"name": "api_server_username",
"message": "Insert api-server username",
"default": "freqtrader",
"when": lambda x: x["api_server"],
},
{
"type": "password",
"name": "api_server_password",
"message": "Insert api-server password",
"when": lambda x: x["api_server"],
},
]
answers = prompt(questions)
if not answers:
# Interrupted questionary sessions return an empty dict.
raise OperationalException("User interrupted interactive questions.")
# Ensure default is set for non-futures exchanges
answers["trading_mode"] = answers.get("trading_mode", "spot")
answers["margin_mode"] = "isolated" if answers.get("trading_mode") == "futures" else ""
# Force JWT token to be a random string
answers["api_server_jwt_key"] = secrets.token_hex()
answers["api_server_ws_token"] = secrets.token_urlsafe(25)
return answers
def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
"""
Applies selections to the template and writes the result to config_path
:param config_path: Path object for new config file. Should not exist yet
:param selections: Dict containing selections taken by the user.
"""
from jinja2.exceptions import TemplateNotFound
from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS
from freqtrade.util import render_template
try:
exchange_template = MAP_EXCHANGE_CHILDCLASS.get(
selections["exchange_name"], selections["exchange_name"]
)
selections["exchange"] = render_template(
templatefile=f"subtemplates/exchange_{exchange_template}.j2", arguments=selections
)
except TemplateNotFound:
selections["exchange"] = render_template(
templatefile="subtemplates/exchange_generic.j2", arguments=selections
)
config_text = render_template(templatefile="base_config.json.j2", arguments=selections)
logger.info(f"Writing config to `{config_path}`.")
logger.info(
"Please make sure to check the configuration contents and adjust settings to your needs."
)
config_path.write_text(config_text)

View File

@@ -15,11 +15,10 @@ if sys.version_info < (3, 10): # pragma: no cover
from freqtrade import __version__ from freqtrade import __version__
from freqtrade.commands import Arguments from freqtrade.commands import Arguments
from freqtrade.configuration import asyncio_setup
from freqtrade.constants import DOCS_LINK from freqtrade.constants import DOCS_LINK
from freqtrade.exceptions import ConfigurationError, FreqtradeException, OperationalException from freqtrade.exceptions import ConfigurationError, FreqtradeException, OperationalException
from freqtrade.loggers import setup_logging_pre from freqtrade.loggers import setup_logging_pre
from freqtrade.util.gc_setup import gc_set_threshold from freqtrade.system import asyncio_setup, gc_set_threshold
logger = logging.getLogger("freqtrade") logger = logging.getLogger("freqtrade")

View File

@@ -21,7 +21,7 @@ async def fallback():
@router_ui.get("/ui_version", include_in_schema=False) @router_ui.get("/ui_version", include_in_schema=False)
async def ui_version(): async def ui_version():
from freqtrade.commands.deploy_commands import read_ui_version from freqtrade.commands.deploy_ui import read_ui_version
uibase = Path(__file__).parent / "ui/installed/" uibase = Path(__file__).parent / "ui/installed/"
version = read_ui_version(uibase) version = read_ui_version(uibase)

View File

@@ -0,0 +1,7 @@
"""system specific and performance tuning"""
from freqtrade.system.asyncio_config import asyncio_setup
from freqtrade.system.gc_setup import gc_set_threshold
__all__ = ["asyncio_setup", "gc_set_threshold"]

View File

@@ -2,7 +2,6 @@ import logging
from typing import Optional from typing import Optional
from freqtrade.constants import Config from freqtrade.constants import Config
from freqtrade.data.history import get_datahandler
from freqtrade.enums import TradingMode from freqtrade.enums import TradingMode
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
@@ -11,6 +10,8 @@ logger = logging.getLogger(__name__)
def migrate_funding_fee_timeframe(config: Config, exchange: Optional[Exchange]): def migrate_funding_fee_timeframe(config: Config, exchange: Optional[Exchange]):
from freqtrade.data.history import get_datahandler
if config.get("trading_mode", TradingMode.SPOT) != TradingMode.FUTURES: if config.get("trading_mode", TradingMode.SPOT) != TradingMode.FUTURES:
# only act on futures # only act on futures
return return

View File

@@ -4,10 +4,10 @@ from unittest.mock import MagicMock
import pytest import pytest
import rapidjson import rapidjson
from freqtrade.commands.build_config_commands import ( from freqtrade.commands.build_config_commands import start_new_config
from freqtrade.configuration.deploy_config import (
ask_user_config, ask_user_config,
ask_user_overwrite, ask_user_overwrite,
start_new_config,
validate_is_float, validate_is_float,
validate_is_int, validate_is_int,
) )
@@ -39,7 +39,7 @@ def test_start_new_config(mocker, caplog, exchange):
wt_mock = mocker.patch.object(Path, "write_text", MagicMock()) wt_mock = mocker.patch.object(Path, "write_text", MagicMock())
mocker.patch.object(Path, "exists", MagicMock(return_value=True)) mocker.patch.object(Path, "exists", MagicMock(return_value=True))
unlink_mock = mocker.patch.object(Path, "unlink", MagicMock()) unlink_mock = mocker.patch.object(Path, "unlink", MagicMock())
mocker.patch("freqtrade.commands.build_config_commands.ask_user_overwrite", return_value=True) mocker.patch("freqtrade.configuration.deploy_config.ask_user_overwrite", return_value=True)
sample_selections = { sample_selections = {
"max_open_trades": 3, "max_open_trades": 3,
@@ -62,7 +62,7 @@ def test_start_new_config(mocker, caplog, exchange):
"api_server_password": "MoneyMachine", "api_server_password": "MoneyMachine",
} }
mocker.patch( mocker.patch(
"freqtrade.commands.build_config_commands.ask_user_config", return_value=sample_selections "freqtrade.configuration.deploy_config.ask_user_config", return_value=sample_selections
) )
args = ["new-config", "--config", "coolconfig.json"] args = ["new-config", "--config", "coolconfig.json"]
start_new_config(get_args(args)) start_new_config(get_args(args))
@@ -80,7 +80,7 @@ def test_start_new_config(mocker, caplog, exchange):
def test_start_new_config_exists(mocker, caplog): def test_start_new_config_exists(mocker, caplog):
mocker.patch.object(Path, "exists", MagicMock(return_value=True)) mocker.patch.object(Path, "exists", MagicMock(return_value=True))
mocker.patch("freqtrade.commands.build_config_commands.ask_user_overwrite", return_value=False) mocker.patch("freqtrade.configuration.deploy_config.ask_user_overwrite", return_value=False)
args = ["new-config", "--config", "coolconfig.json"] args = ["new-config", "--config", "coolconfig.json"]
with pytest.raises(OperationalException, match=r"Configuration .* already exists\."): with pytest.raises(OperationalException, match=r"Configuration .* already exists\."):
start_new_config(get_args(args)) start_new_config(get_args(args))
@@ -91,14 +91,14 @@ def test_ask_user_overwrite(mocker):
Once https://github.com/tmbo/questionary/issues/35 is implemented, improve this test. Once https://github.com/tmbo/questionary/issues/35 is implemented, improve this test.
""" """
prompt_mock = mocker.patch( prompt_mock = mocker.patch(
"freqtrade.commands.build_config_commands.prompt", return_value={"overwrite": False} "freqtrade.configuration.deploy_config.prompt", return_value={"overwrite": False}
) )
assert not ask_user_overwrite(Path("test.json")) assert not ask_user_overwrite(Path("test.json"))
assert prompt_mock.call_count == 1 assert prompt_mock.call_count == 1
prompt_mock.reset_mock() prompt_mock.reset_mock()
prompt_mock = mocker.patch( prompt_mock = mocker.patch(
"freqtrade.commands.build_config_commands.prompt", return_value={"overwrite": True} "freqtrade.configuration.deploy_config.prompt", return_value={"overwrite": True}
) )
assert ask_user_overwrite(Path("test.json")) assert ask_user_overwrite(Path("test.json"))
assert prompt_mock.call_count == 1 assert prompt_mock.call_count == 1
@@ -109,13 +109,13 @@ def test_ask_user_config(mocker):
Once https://github.com/tmbo/questionary/issues/35 is implemented, improve this test. Once https://github.com/tmbo/questionary/issues/35 is implemented, improve this test.
""" """
prompt_mock = mocker.patch( prompt_mock = mocker.patch(
"freqtrade.commands.build_config_commands.prompt", return_value={"overwrite": False} "freqtrade.configuration.deploy_config.prompt", return_value={"overwrite": False}
) )
answers = ask_user_config() answers = ask_user_config()
assert isinstance(answers, dict) assert isinstance(answers, dict)
assert prompt_mock.call_count == 1 assert prompt_mock.call_count == 1
prompt_mock = mocker.patch("freqtrade.commands.build_config_commands.prompt", return_value={}) prompt_mock = mocker.patch("freqtrade.configuration.deploy_config.prompt", return_value={})
with pytest.raises(OperationalException, match=r"User interrupted interactive questions\."): with pytest.raises(OperationalException, match=r"User interrupted interactive questions\."):
ask_user_config() ask_user_config()

View File

@@ -31,7 +31,7 @@ from freqtrade.commands import (
start_webserver, start_webserver,
) )
from freqtrade.commands.db_commands import start_convert_db from freqtrade.commands.db_commands import start_convert_db
from freqtrade.commands.deploy_commands import ( from freqtrade.commands.deploy_ui import (
clean_ui_subdir, clean_ui_subdir,
download_and_install_ui, download_and_install_ui,
get_ui_download_url, get_ui_download_url,
@@ -571,8 +571,12 @@ def test_create_datadir_failed(caplog):
def test_create_datadir(caplog, mocker): def test_create_datadir(caplog, mocker):
cud = mocker.patch("freqtrade.commands.deploy_commands.create_userdata_dir", MagicMock()) cud = mocker.patch(
csf = mocker.patch("freqtrade.commands.deploy_commands.copy_sample_files", MagicMock()) "freqtrade.configuration.directory_operations.create_userdata_dir", MagicMock()
)
csf = mocker.patch(
"freqtrade.configuration.directory_operations.copy_sample_files", MagicMock()
)
args = ["create-userdir", "--userdir", "/temp/freqtrade/test"] args = ["create-userdir", "--userdir", "/temp/freqtrade/test"]
start_create_userdir(get_args(args)) start_create_userdir(get_args(args))
@@ -591,7 +595,7 @@ def test_start_new_strategy(mocker, caplog):
assert "CoolNewStrategy" in wt_mock.call_args_list[0][0][0] assert "CoolNewStrategy" in wt_mock.call_args_list[0][0][0]
assert log_has_re("Writing strategy to .*", caplog) assert log_has_re("Writing strategy to .*", caplog)
mocker.patch("freqtrade.commands.deploy_commands.setup_utils_configuration") mocker.patch("freqtrade.configuration.setup_utils_configuration")
mocker.patch.object(Path, "exists", MagicMock(return_value=True)) mocker.patch.object(Path, "exists", MagicMock(return_value=True))
with pytest.raises( with pytest.raises(
OperationalException, match=r".* already exists. Please choose another Strategy Name\." OperationalException, match=r".* already exists. Please choose another Strategy Name\."
@@ -608,13 +612,13 @@ def test_start_new_strategy_no_arg(mocker, caplog):
def test_start_install_ui(mocker): def test_start_install_ui(mocker):
clean_mock = mocker.patch("freqtrade.commands.deploy_commands.clean_ui_subdir") clean_mock = mocker.patch("freqtrade.commands.deploy_ui.clean_ui_subdir")
get_url_mock = mocker.patch( get_url_mock = mocker.patch(
"freqtrade.commands.deploy_commands.get_ui_download_url", "freqtrade.commands.deploy_ui.get_ui_download_url",
return_value=("https://example.com/whatever", "0.0.1"), return_value=("https://example.com/whatever", "0.0.1"),
) )
download_mock = mocker.patch("freqtrade.commands.deploy_commands.download_and_install_ui") download_mock = mocker.patch("freqtrade.commands.deploy_ui.download_and_install_ui")
mocker.patch("freqtrade.commands.deploy_commands.read_ui_version", return_value=None) mocker.patch("freqtrade.commands.deploy_ui.read_ui_version", return_value=None)
args = [ args = [
"install-ui", "install-ui",
] ]
@@ -638,13 +642,13 @@ def test_start_install_ui(mocker):
def test_clean_ui_subdir(mocker, tmp_path, caplog): def test_clean_ui_subdir(mocker, tmp_path, caplog):
mocker.patch("freqtrade.commands.deploy_commands.Path.is_dir", side_effect=[True, True]) mocker.patch("freqtrade.commands.deploy_ui.Path.is_dir", side_effect=[True, True])
mocker.patch("freqtrade.commands.deploy_commands.Path.is_file", side_effect=[False, True]) mocker.patch("freqtrade.commands.deploy_ui.Path.is_file", side_effect=[False, True])
rd_mock = mocker.patch("freqtrade.commands.deploy_commands.Path.rmdir") rd_mock = mocker.patch("freqtrade.commands.deploy_ui.Path.rmdir")
ul_mock = mocker.patch("freqtrade.commands.deploy_commands.Path.unlink") ul_mock = mocker.patch("freqtrade.commands.deploy_ui.Path.unlink")
mocker.patch( mocker.patch(
"freqtrade.commands.deploy_commands.Path.glob", "freqtrade.commands.deploy_ui.Path.glob",
return_value=[Path("test1"), Path("test2"), Path(".gitkeep")], return_value=[Path("test1"), Path("test2"), Path(".gitkeep")],
) )
folder = tmp_path / "uitests" folder = tmp_path / "uitests"
@@ -664,10 +668,10 @@ def test_download_and_install_ui(mocker, tmp_path):
file_like_object.seek(0) file_like_object.seek(0)
requests_mock.content = file_like_object.read() requests_mock.content = file_like_object.read()
mocker.patch("freqtrade.commands.deploy_commands.requests.get", return_value=requests_mock) mocker.patch("freqtrade.commands.deploy_ui.requests.get", return_value=requests_mock)
mocker.patch("freqtrade.commands.deploy_commands.Path.is_dir", side_effect=[True, False]) mocker.patch("freqtrade.commands.deploy_ui.Path.is_dir", side_effect=[True, False])
wb_mock = mocker.patch("freqtrade.commands.deploy_commands.Path.write_bytes") wb_mock = mocker.patch("freqtrade.commands.deploy_ui.Path.write_bytes")
folder = tmp_path / "uitests_dl" folder = tmp_path / "uitests_dl"
folder.mkdir(exist_ok=True) folder.mkdir(exist_ok=True)
@@ -689,9 +693,7 @@ def test_get_ui_download_url(mocker):
[{"browser_download_url": "http://download.zip"}], [{"browser_download_url": "http://download.zip"}],
] ]
) )
get_mock = mocker.patch( get_mock = mocker.patch("freqtrade.commands.deploy_ui.requests.get", return_value=response)
"freqtrade.commands.deploy_commands.requests.get", return_value=response
)
x, last_version = get_ui_download_url() x, last_version = get_ui_download_url()
assert get_mock.call_count == 2 assert get_mock.call_count == 2
assert last_version == "0.0.1" assert last_version == "0.0.1"
@@ -714,9 +716,7 @@ def test_get_ui_download_url_direct(mocker):
}, },
] ]
) )
get_mock = mocker.patch( get_mock = mocker.patch("freqtrade.commands.deploy_ui.requests.get", return_value=response)
"freqtrade.commands.deploy_commands.requests.get", return_value=response
)
x, last_version = get_ui_download_url() x, last_version = get_ui_download_url()
assert get_mock.call_count == 1 assert get_mock.call_count == 1
assert last_version == "0.0.2" assert last_version == "0.0.2"
@@ -734,7 +734,7 @@ def test_get_ui_download_url_direct(mocker):
def test_download_data_keyboardInterrupt(mocker, markets): def test_download_data_keyboardInterrupt(mocker, markets):
dl_mock = mocker.patch( dl_mock = mocker.patch(
"freqtrade.commands.data_commands.download_data_main", "freqtrade.data.history.download_data_main",
MagicMock(side_effect=KeyboardInterrupt), MagicMock(side_effect=KeyboardInterrupt),
) )
patch_exchange(mocker) patch_exchange(mocker)
@@ -972,7 +972,7 @@ def test_download_data_data_invalid(mocker):
def test_start_convert_trades(mocker): def test_start_convert_trades(mocker):
convert_mock = mocker.patch( convert_mock = mocker.patch(
"freqtrade.commands.data_commands.convert_trades_to_ohlcv", MagicMock(return_value=[]) "freqtrade.data.converter.convert_trades_to_ohlcv", MagicMock(return_value=[])
) )
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch(f"{EXMS}.get_markets") mocker.patch(f"{EXMS}.get_markets")
@@ -1522,7 +1522,7 @@ def test_hyperopt_show(mocker, capsys):
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt_tools.HyperoptTools._read_results", side_effect=fake_iterator "freqtrade.optimize.hyperopt_tools.HyperoptTools._read_results", side_effect=fake_iterator
) )
mocker.patch("freqtrade.commands.hyperopt_commands.show_backtest_result") mocker.patch("freqtrade.optimize.optimize_reports.show_backtest_result")
args = [ args = [
"hyperopt-show", "hyperopt-show",
@@ -1579,8 +1579,8 @@ def test_hyperopt_show(mocker, capsys):
def test_convert_data(mocker, testdatadir): def test_convert_data(mocker, testdatadir):
ohlcv_mock = mocker.patch("freqtrade.commands.data_commands.convert_ohlcv_format") ohlcv_mock = mocker.patch("freqtrade.data.converter.convert_ohlcv_format")
trades_mock = mocker.patch("freqtrade.commands.data_commands.convert_trades_format") trades_mock = mocker.patch("freqtrade.data.converter.convert_trades_format")
args = [ args = [
"convert-data", "convert-data",
"--format-from", "--format-from",
@@ -1601,8 +1601,8 @@ def test_convert_data(mocker, testdatadir):
def test_convert_data_trades(mocker, testdatadir): def test_convert_data_trades(mocker, testdatadir):
ohlcv_mock = mocker.patch("freqtrade.commands.data_commands.convert_ohlcv_format") ohlcv_mock = mocker.patch("freqtrade.data.converter.convert_ohlcv_format")
trades_mock = mocker.patch("freqtrade.commands.data_commands.convert_trades_format") trades_mock = mocker.patch("freqtrade.data.converter.convert_trades_format")
args = [ args = [
"convert-trade-data", "convert-trade-data",
"--format-from", "--format-from",

View File

@@ -0,0 +1,17 @@
import subprocess
import time
MAXIMUM_STARTUP_TIME = 0.5
def test_startup_time():
# warm up to generate pyc
subprocess.run(["freqtrade", "-h"])
start = time.time()
subprocess.run(["freqtrade", "-h"])
elapsed = time.time() - start
assert (
elapsed < MAXIMUM_STARTUP_TIME
), "The startup time is too long, try to use lazy import in the command entry function"

View File

@@ -185,7 +185,7 @@ def test_api_ui_fallback(botclient, mocker):
def test_api_ui_version(botclient, mocker): def test_api_ui_version(botclient, mocker):
_ftbot, client = botclient _ftbot, client = botclient
mocker.patch("freqtrade.commands.deploy_commands.read_ui_version", return_value="0.1.2") mocker.patch("freqtrade.commands.deploy_ui.read_ui_version", return_value="0.1.2")
rc = client_get(client, "/ui_version") rc = client_get(client, "/ui_version")
assert rc.status_code == 200 assert rc.status_code == 200
assert rc.json()["version"] == "0.1.2" assert rc.json()["version"] == "0.1.2"

View File

@@ -121,7 +121,7 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None:
def test_main_operational_exception1(mocker, default_conf, caplog) -> None: def test_main_operational_exception1(mocker, default_conf, caplog) -> None:
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch( mocker.patch(
"freqtrade.commands.list_commands.list_available_exchanges", "freqtrade.exchange.list_available_exchanges",
MagicMock(side_effect=ValueError("Oh snap!")), MagicMock(side_effect=ValueError("Oh snap!")),
) )
patched_configuration_load_config_file(mocker, default_conf) patched_configuration_load_config_file(mocker, default_conf)
@@ -135,7 +135,7 @@ def test_main_operational_exception1(mocker, default_conf, caplog) -> None:
assert log_has("Fatal exception!", caplog) assert log_has("Fatal exception!", caplog)
assert not log_has_re(r"SIGINT.*", caplog) assert not log_has_re(r"SIGINT.*", caplog)
mocker.patch( mocker.patch(
"freqtrade.commands.list_commands.list_available_exchanges", "freqtrade.exchange.list_available_exchanges",
MagicMock(side_effect=KeyboardInterrupt), MagicMock(side_effect=KeyboardInterrupt),
) )
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
@@ -147,7 +147,7 @@ def test_main_operational_exception1(mocker, default_conf, caplog) -> None:
def test_main_ConfigurationError(mocker, default_conf, caplog) -> None: def test_main_ConfigurationError(mocker, default_conf, caplog) -> None:
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch( mocker.patch(
"freqtrade.commands.list_commands.list_available_exchanges", "freqtrade.exchange.list_available_exchanges",
MagicMock(side_effect=ConfigurationError("Oh snap!")), MagicMock(side_effect=ConfigurationError("Oh snap!")),
) )
patched_configuration_load_config_file(mocker, default_conf) patched_configuration_load_config_file(mocker, default_conf)