mirror of
https://github.com/freqtrade/freqtrade.git
synced 2026-01-29 10:20:25 +00:00
Merge pull request #10746 from xzmeng/startup-time
Postpone imports on demand
This commit is contained in:
@@ -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 = {
|
||||||
|
|||||||
@@ -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:")
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
|||||||
84
freqtrade/commands/deploy_ui.py
Normal file
84
freqtrade/commands/deploy_ui.py
Normal 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
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
250
freqtrade/configuration/deploy_config.py
Normal file
250
freqtrade/configuration/deploy_config.py
Normal 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)
|
||||||
@@ -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")
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
7
freqtrade/system/__init__.py
Normal file
7
freqtrade/system/__init__.py
Normal 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"]
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
17
tests/commands/test_startup_time.py
Normal file
17
tests/commands/test_startup_time.py
Normal 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"
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user