Merge branch 'develop' into pr/rextea/4606

This commit is contained in:
Matthias
2021-10-17 16:29:19 +02:00
287 changed files with 17159 additions and 9644 deletions

View File

@@ -8,15 +8,16 @@ Note: Be careful with file-scoped imports in these subfiles.
"""
from freqtrade.commands.arguments import Arguments
from freqtrade.commands.build_config_commands import start_new_config
from freqtrade.commands.data_commands import (start_convert_data, start_download_data,
start_list_data)
from freqtrade.commands.data_commands import (start_convert_data, start_convert_trades,
start_download_data, start_list_data)
from freqtrade.commands.deploy_commands import (start_create_userdir, start_install_ui,
start_new_hyperopt, start_new_strategy)
start_new_strategy)
from freqtrade.commands.hyperopt_commands import start_hyperopt_list, start_hyperopt_show
from freqtrade.commands.list_commands import (start_list_exchanges, start_list_hyperopts,
start_list_markets, start_list_strategies,
start_list_timeframes, start_show_trades)
from freqtrade.commands.list_commands import (start_list_exchanges, start_list_markets,
start_list_strategies, start_list_timeframes,
start_show_trades)
from freqtrade.commands.optimize_commands import start_backtesting, start_edge, start_hyperopt
from freqtrade.commands.pairlist_commands import start_test_pairlist
from freqtrade.commands.plot_commands import start_plot_dataframe, start_plot_profit
from freqtrade.commands.trade_commands import start_trading
from freqtrade.commands.webserver_commands import start_webserver

View File

@@ -16,11 +16,13 @@ ARGS_STRATEGY = ["strategy", "strategy_path"]
ARGS_TRADE = ["db_url", "sd_notify", "dry_run", "dry_run_wallet", "fee"]
ARGS_WEBSERVER: List[str] = []
ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv",
"max_open_trades", "stake_amount", "fee"]
"max_open_trades", "stake_amount", "fee", "pairs"]
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
"enable_protections", "dry_run_wallet",
"enable_protections", "dry_run_wallet", "timeframe_detail",
"strategy_list", "export", "exportfilename", "show_days"]
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
@@ -29,7 +31,8 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
"epochs", "spaces", "print_all",
"print_colorized", "print_json", "hyperopt_jobs",
"hyperopt_random_state", "hyperopt_min_trades",
"hyperopt_loss"]
"hyperopt_loss", "disableparamexport",
"hyperopt_ignore_missing_space"]
ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"]
@@ -53,24 +56,25 @@ ARGS_BUILD_CONFIG = ["config"]
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
ARGS_BUILD_HYPEROPT = ["user_data_dir", "hyperopt", "template"]
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase"]
ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes"]
ARGS_CONVERT_TRADES = ["pairs", "timeframes", "exchange", "dataformat_ohlcv", "dataformat_trades"]
ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs"]
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "timerange", "download_trades", "exchange",
"timeframes", "erase", "dataformat_ohlcv", "dataformat_trades"]
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "new_pairs_days", "timerange",
"download_trades", "exchange", "timeframes", "erase", "dataformat_ohlcv",
"dataformat_trades"]
ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit",
"db_url", "trade_source", "export", "exportfilename",
"timerange", "timeframe", "no_trades"]
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
"trade_source", "timeframe"]
"trade_source", "timeframe", "plot_auto_open"]
ARGS_INSTALL_UI = ["erase_ui_only"]
ARGS_INSTALL_UI = ["erase_ui_only", 'ui_version']
ARGS_SHOW_TRADES = ["db_url", "trade_ids", "print_json"]
@@ -84,14 +88,15 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable",
"hyperoptexportfilename", "export_csv"]
ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index",
"print_json", "hyperoptexportfilename", "hyperopt_show_no_header"]
"print_json", "hyperoptexportfilename", "hyperopt_show_no_header",
"disableparamexport"]
NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes",
"list-markets", "list-pairs", "list-strategies", "list-data",
"list-hyperopts", "hyperopt-list", "hyperopt-show",
"plot-dataframe", "plot-profit", "show-trades"]
"hyperopt-list", "hyperopt-show",
"plot-dataframe", "plot-profit", "show-trades", "trades-to-ohlcv"]
NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-hyperopt", "new-strategy"]
NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-strategy"]
class Arguments:
@@ -167,14 +172,14 @@ class Arguments:
self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot')
self._build_args(optionlist=['version'], parser=self.parser)
from freqtrade.commands import (start_backtesting, start_convert_data, start_create_userdir,
start_download_data, start_edge, start_hyperopt,
start_hyperopt_list, start_hyperopt_show, start_install_ui,
start_list_data, start_list_exchanges, start_list_hyperopts,
from freqtrade.commands import (start_backtesting, start_convert_data, start_convert_trades,
start_create_userdir, start_download_data, start_edge,
start_hyperopt, start_hyperopt_list, start_hyperopt_show,
start_install_ui, start_list_data, start_list_exchanges,
start_list_markets, start_list_strategies,
start_list_timeframes, start_new_config, start_new_hyperopt,
start_new_strategy, start_plot_dataframe, start_plot_profit,
start_show_trades, start_test_pairlist, start_trading)
start_list_timeframes, start_new_config, start_new_strategy,
start_plot_dataframe, start_plot_profit, start_show_trades,
start_test_pairlist, start_trading, start_webserver)
subparsers = self.parser.add_subparsers(dest='command',
# Use custom message when no subhandler is added
@@ -201,12 +206,6 @@ class Arguments:
build_config_cmd.set_defaults(func=start_new_config)
self._build_args(optionlist=ARGS_BUILD_CONFIG, parser=build_config_cmd)
# add new-hyperopt subcommand
build_hyperopt_cmd = subparsers.add_parser('new-hyperopt',
help="Create new hyperopt")
build_hyperopt_cmd.set_defaults(func=start_new_hyperopt)
self._build_args(optionlist=ARGS_BUILD_HYPEROPT, parser=build_hyperopt_cmd)
# add new-strategy subcommand
build_strategy_cmd = subparsers.add_parser('new-strategy',
help="Create new strategy")
@@ -240,6 +239,15 @@ class Arguments:
convert_trade_data_cmd.set_defaults(func=partial(start_convert_data, ohlcv=False))
self._build_args(optionlist=ARGS_CONVERT_DATA, parser=convert_trade_data_cmd)
# Add trades-to-ohlcv subcommand
convert_trade_data_cmd = subparsers.add_parser(
'trades-to-ohlcv',
help='Convert trade data to OHLCV data.',
parents=[_common_parser],
)
convert_trade_data_cmd.set_defaults(func=start_convert_trades)
self._build_args(optionlist=ARGS_CONVERT_TRADES, parser=convert_trade_data_cmd)
# Add list-data subcommand
list_data_cmd = subparsers.add_parser(
'list-data',
@@ -295,15 +303,6 @@ class Arguments:
list_exchanges_cmd.set_defaults(func=start_list_exchanges)
self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd)
# Add list-hyperopts subcommand
list_hyperopts_cmd = subparsers.add_parser(
'list-hyperopts',
help='Print available hyperopt classes.',
parents=[_common_parser],
)
list_hyperopts_cmd.set_defaults(func=start_list_hyperopts)
self._build_args(optionlist=ARGS_LIST_HYPEROPTS, parser=list_hyperopts_cmd)
# Add list-markets subcommand
list_markets_cmd = subparsers.add_parser(
'list-markets',
@@ -382,3 +381,9 @@ class Arguments:
)
plot_profit_cmd.set_defaults(func=start_plot_profit)
self._build_args(optionlist=ARGS_PLOT_PROFIT, parser=plot_profit_cmd)
# Add webserver subcommand
webserver_cmd = subparsers.add_parser('webserver', help='Webserver module.',
parents=[_common_parser])
webserver_cmd.set_defaults(func=start_webserver)
self._build_args(optionlist=ARGS_WEBSERVER, parser=webserver_cmd)

View File

@@ -1,9 +1,11 @@
import logging
import secrets
from pathlib import Path
from typing import Any, Dict, List
from questionary import Separator, prompt
from freqtrade.configuration.directory_operations import chown_user_directory
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS, available_exchanges
@@ -59,21 +61,27 @@ def ask_user_config() -> Dict[str, Any]:
"type": "text",
"name": "stake_currency",
"message": "Please insert your stake currency:",
"default": 'BTC',
"default": 'USDT',
},
{
"type": "text",
"name": "stake_amount",
"message": "Please insert your stake amount:",
"default": "0.01",
"message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):",
"default": "100",
"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": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):",
"default": "3",
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val)
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val),
"filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"'
if val == UNLIMITED_STAKE_AMOUNT
else val
},
{
"type": "text",
@@ -97,6 +105,8 @@ def ask_user_config() -> Dict[str, Any]:
"bittrex",
"kraken",
"ftx",
"kucoin",
"gateio",
Separator(),
"other",
],
@@ -120,6 +130,12 @@ def ask_user_config() -> Dict[str, Any]:
"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'] == 'kucoin'
},
{
"type": "confirm",
"name": "telegram",
@@ -138,6 +154,33 @@ def ask_user_config() -> Dict[str, Any]:
"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",
"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": "text",
"name": "api_server_password",
"message": "Insert api-server password",
"when": lambda x: x['api_server']
},
]
answers = prompt(questions)
@@ -145,6 +188,9 @@ def ask_user_config() -> Dict[str, Any]:
# Interrupted questionary sessions return an empty dict.
raise OperationalException("User interrupted interactive questions.")
# Force JWT token to be a random string
answers['api_server_jwt_key'] = secrets.token_hex()
return answers
@@ -152,7 +198,7 @@ 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 selecions: Dict containing selections taken by the user.
:param selections: Dict containing selections taken by the user.
"""
from jinja2.exceptions import TemplateNotFound
try:
@@ -162,7 +208,7 @@ def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
selections['exchange'] = render_template(
templatefile=f"subtemplates/exchange_{exchange_template}.j2",
arguments=selections
)
)
except TemplateNotFound:
selections['exchange'] = render_template(
templatefile="subtemplates/exchange_generic.j2",
@@ -182,10 +228,11 @@ def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
def start_new_config(args: Dict[str, Any]) -> None:
"""
Create a new strategy from a template
Asking the user questions to fill out the templateaccordingly.
Asking the user questions to fill out the template accordingly.
"""
config_path = Path(args['config'][0])
chown_user_directory(config_path.parent)
if config_path.exists():
overwrite = ask_user_overwrite(config_path)
if overwrite:

View File

@@ -1,7 +1,7 @@
"""
Definition of cli arguments used in arguments.py
"""
from argparse import ArgumentTypeError
from argparse import SUPPRESS, ArgumentTypeError
from freqtrade import __version__, constants
from freqtrade.constants import HYPEROPT_LOSS_BUILTIN
@@ -118,7 +118,7 @@ AVAILABLE_CLI_OPTIONS = {
# Optimize common
"timeframe": Arg(
'-i', '--timeframe', '--ticker-interval',
help='Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`).',
help='Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).',
),
"timerange": Arg(
'--timerange',
@@ -135,6 +135,10 @@ AVAILABLE_CLI_OPTIONS = {
help='Override the value of the `stake_amount` configuration setting.',
),
# Backtesting
"timeframe_detail": Arg(
'--timeframe-detail',
help='Specify detail timeframe for backtesting (`1m`, `5m`, `30m`, `1h`, `1d`).',
),
"position_stacking": Arg(
'--eps', '--enable-position-stacking',
help='Allow buying the same pair multiple times (position stacking).',
@@ -162,13 +166,14 @@ AVAILABLE_CLI_OPTIONS = {
'Please note that ticker-interval needs to be set either in config '
'or via command line. When using this together with `--export trades`, '
'the strategy-name is injected into the filename '
'(so `backtest-data.json` becomes `backtest-data-DefaultStrategy.json`',
'(so `backtest-data.json` becomes `backtest-data-SampleStrategy.json`',
nargs='+',
),
"export": Arg(
'--export',
help='Export backtest results, argument are: trades. '
'Example: `--export=trades`',
help='Export backtest results (default: trades).',
choices=constants.EXPORT_OPTIONS,
),
"exportfilename": Arg(
'--export-filename',
@@ -177,6 +182,11 @@ AVAILABLE_CLI_OPTIONS = {
'Example: `--export-filename=user_data/backtest_results/backtest_today.json`',
metavar='PATH',
),
"disableparamexport": Arg(
'--disable-param-export',
help="Disable automatic hyperopt parameter export.",
action='store_true',
),
"fee": Arg(
'--fee',
help='Specify fee ratio. Will be applied twice (on trade entry and exit).',
@@ -199,12 +209,13 @@ AVAILABLE_CLI_OPTIONS = {
# Hyperopt
"hyperopt": Arg(
'--hyperopt',
help='Specify hyperopt class name which will be used by the bot.',
help=SUPPRESS,
metavar='NAME',
required=False,
),
"hyperopt_path": Arg(
'--hyperopt-path',
help='Specify additional lookup path for Hyperopt and Hyperopt Loss functions.',
help='Specify additional lookup path for Hyperopt Loss functions.',
metavar='PATH',
),
"epochs": Arg(
@@ -217,7 +228,7 @@ AVAILABLE_CLI_OPTIONS = {
"spaces": Arg(
'--spaces',
help='Specify which parameters to hyperopt. Space-separated list.',
choices=['all', 'buy', 'sell', 'roi', 'stoploss', 'trailing', 'default'],
choices=['all', 'buy', 'sell', 'roi', 'stoploss', 'trailing', 'protection', 'default'],
nargs='+',
default='default',
),
@@ -272,7 +283,7 @@ AVAILABLE_CLI_OPTIONS = {
default=1,
),
"hyperopt_loss": Arg(
'--hyperopt-loss',
'--hyperopt-loss', '--hyperoptloss',
help='Specify the class name of the hyperopt loss function class (IHyperOptLoss). '
'Different functions can generate completely different results, '
'since the target for optimization is different. Built-in Hyperopt-loss-functions are: '
@@ -335,7 +346,7 @@ AVAILABLE_CLI_OPTIONS = {
# Script options
"pairs": Arg(
'-p', '--pairs',
help='Show profits for only these pairs. Pairs are space-separated.',
help='Limit command to these pairs. Pairs are space-separated.',
nargs='+',
),
# Download data
@@ -350,6 +361,12 @@ AVAILABLE_CLI_OPTIONS = {
type=check_int_positive,
metavar='INT',
),
"new_pairs_days": Arg(
'--new-pairs-days',
help='Download data of new pairs for given number of days. Default: `%(default)s`.',
type=check_int_positive,
metavar='INT',
),
"download_trades": Arg(
'--dl-trades',
help='Download trades instead of OHLCV data. The bot will resample trades to the '
@@ -370,12 +387,12 @@ AVAILABLE_CLI_OPTIONS = {
),
"dataformat_ohlcv": Arg(
'--data-format-ohlcv',
help='Storage format for downloaded candle (OHLCV) data. (default: `%(default)s`).',
help='Storage format for downloaded candle (OHLCV) data. (default: `json`).',
choices=constants.AVAILABLE_DATAHANDLERS,
),
"dataformat_trades": Arg(
'--data-format-trades',
help='Storage format for downloaded trades data. (default: `%(default)s`).',
help='Storage format for downloaded trades data. (default: `jsongz`).',
choices=constants.AVAILABLE_DATAHANDLERS,
),
"exchange": Arg(
@@ -403,6 +420,12 @@ AVAILABLE_CLI_OPTIONS = {
action='store_true',
default=False,
),
"ui_version": Arg(
'--ui-version',
help=('Specify a specific version of FreqUI to install. '
'Not specifying this installs the latest version.'),
type=str,
),
# Templating options
"template": Arg(
'--template',
@@ -432,6 +455,11 @@ AVAILABLE_CLI_OPTIONS = {
metavar='INT',
default=750,
),
"plot_auto_open": Arg(
'--auto-open',
help='Automatically open generated plot.',
action='store_true',
),
"no_trades": Arg(
'--no-trades',
help='Skip using trades from backtesting file and DB.',
@@ -536,4 +564,10 @@ AVAILABLE_CLI_OPTIONS = {
help='Do not print epoch details header.',
action='store_true',
),
"hyperopt_ignore_missing_space": Arg(
"--ignore-missing-spaces", "--ignore-unparameterized-spaces",
help=("Suppress errors for any requested Hyperopt spaces "
"that do not contain any parameters."),
action="store_true",
),
}

View File

@@ -8,11 +8,11 @@ from freqtrade.configuration import TimeRange, setup_utils_configuration
from freqtrade.data.converter import convert_ohlcv_format, convert_trades_format
from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_ohlcv_data,
refresh_backtest_trades_data)
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.resolvers import ExchangeResolver
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@@ -48,7 +48,8 @@ def start_download_data(args: Dict[str, Any]) -> None:
# Init exchange
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)
# Manual validations of relevant settings
exchange.validate_pairs(config['pairs'])
if not config['exchange'].get('skip_pair_validation', False):
exchange.validate_pairs(config['pairs'])
expanded_pairs = expand_pairlist(config['pairs'], list(exchange.markets))
logger.info(f"About to download pairs: {expanded_pairs}, "
@@ -62,8 +63,8 @@ def start_download_data(args: Dict[str, Any]) -> None:
if config.get('download_trades'):
pairs_not_available = refresh_backtest_trades_data(
exchange, pairs=expanded_pairs, datadir=config['datadir'],
timerange=timerange, erase=bool(config.get('erase')),
data_format=config['dataformat_trades'])
timerange=timerange, new_pairs_days=config['new_pairs_days'],
erase=bool(config.get('erase')), data_format=config['dataformat_trades'])
# Convert downloaded trade data to different timeframes
convert_trades_to_ohlcv(
@@ -75,8 +76,9 @@ def start_download_data(args: Dict[str, Any]) -> None:
else:
pairs_not_available = refresh_backtest_ohlcv_data(
exchange, pairs=expanded_pairs, timeframes=config['timeframes'],
datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
data_format=config['dataformat_ohlcv'])
datadir=config['datadir'], timerange=timerange,
new_pairs_days=config['new_pairs_days'],
erase=bool(config.get('erase')), data_format=config['dataformat_ohlcv'])
except KeyboardInterrupt:
sys.exit("SIGINT received, aborting ...")
@@ -87,6 +89,41 @@ def start_download_data(args: Dict[str, Any]) -> None:
f"on exchange {exchange.name}.")
def start_convert_trades(args: Dict[str, Any]) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
timerange = TimeRange()
# Remove stake-currency to skip checks which are not relevant for datadownload
config['stake_currency'] = ''
if 'pairs' not in config:
raise OperationalException(
"Downloading data requires a list of pairs. "
"Please check the documentation on how to configure this.")
# Init exchange
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)
# Manual validations of relevant settings
if not config['exchange'].get('skip_pair_validation', False):
exchange.validate_pairs(config['pairs'])
expanded_pairs = expand_pairlist(config['pairs'], list(exchange.markets))
logger.info(f"About to Convert pairs: {expanded_pairs}, "
f"intervals: {config['timeframes']} to {config['datadir']}")
for timeframe in config['timeframes']:
exchange.validate_timeframes(timeframe)
# Convert downloaded trade data to different timeframes
convert_trades_to_ohlcv(
pairs=expanded_pairs, timeframes=config['timeframes'],
datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
data_format_ohlcv=config['dataformat_ohlcv'],
data_format_trades=config['dataformat_trades'],
)
def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
"""
Convert data from one format to another

View File

@@ -7,10 +7,10 @@ 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_HYPEROPTS, USERPATH_STRATEGIES
from freqtrade.constants import USERPATH_STRATEGIES
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.misc import render_template, render_template_with_fallback
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@@ -38,15 +38,15 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st
indicators = render_template_with_fallback(
templatefile=f"subtemplates/indicators_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/indicators_{fallback}.j2",
)
)
buy_trend = render_template_with_fallback(
templatefile=f"subtemplates/buy_trend_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/buy_trend_{fallback}.j2",
)
)
sell_trend = render_template_with_fallback(
templatefile=f"subtemplates/sell_trend_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/sell_trend_{fallback}.j2",
)
)
plot_config = render_template_with_fallback(
templatefile=f"subtemplates/plot_config_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/plot_config_{fallback}.j2",
@@ -74,8 +74,6 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
if "strategy" in args and args["strategy"]:
if args["strategy"] == "DefaultStrategy":
raise OperationalException("DefaultStrategy is not allowed as name.")
new_path = config['user_data_dir'] / USERPATH_STRATEGIES / (args['strategy'] + '.py')
@@ -89,58 +87,6 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
raise OperationalException("`new-strategy` requires --strategy to be set.")
def deploy_new_hyperopt(hyperopt_name: str, hyperopt_path: Path, subtemplate: str) -> None:
"""
Deploys a new hyperopt template to hyperopt_path
"""
fallback = 'full'
buy_guards = render_template_with_fallback(
templatefile=f"subtemplates/hyperopt_buy_guards_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/hyperopt_buy_guards_{fallback}.j2",
)
sell_guards = render_template_with_fallback(
templatefile=f"subtemplates/hyperopt_sell_guards_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/hyperopt_sell_guards_{fallback}.j2",
)
buy_space = render_template_with_fallback(
templatefile=f"subtemplates/hyperopt_buy_space_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/hyperopt_buy_space_{fallback}.j2",
)
sell_space = render_template_with_fallback(
templatefile=f"subtemplates/hyperopt_sell_space_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/hyperopt_sell_space_{fallback}.j2",
)
strategy_text = render_template(templatefile='base_hyperopt.py.j2',
arguments={"hyperopt": hyperopt_name,
"buy_guards": buy_guards,
"sell_guards": sell_guards,
"buy_space": buy_space,
"sell_space": sell_space,
})
logger.info(f"Writing hyperopt to `{hyperopt_path}`.")
hyperopt_path.write_text(strategy_text)
def start_new_hyperopt(args: Dict[str, Any]) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
if 'hyperopt' in args and args['hyperopt']:
if args['hyperopt'] == 'DefaultHyperopt':
raise OperationalException("DefaultHyperopt is not allowed as name.")
new_path = config['user_data_dir'] / USERPATH_HYPEROPTS / (args['hyperopt'] + '.py')
if new_path.exists():
raise OperationalException(f"`{new_path}` already exists. "
"Please choose another Hyperopt Name.")
deploy_new_hyperopt(args['hyperopt'], new_path, args['template'])
else:
raise OperationalException("`new-hyperopt` requires --hyperopt to be set.")
def clean_ui_subdir(directory: Path):
if directory.is_dir():
logger.info("Removing UI directory content.")
@@ -182,7 +128,7 @@ def download_and_install_ui(dest_folder: Path, dl_url: str, version: str):
f.write(version)
def get_ui_download_url() -> Tuple[str, str]:
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
@@ -190,8 +136,16 @@ def get_ui_download_url() -> Tuple[str, str]:
resp.raise_for_status()
r = resp.json()
latest_version = r[0]['name']
assets = r[0].get('assets', [])
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']
@@ -210,7 +164,7 @@ def start_install_ui(args: Dict[str, Any]) -> None:
dest_folder = Path(__file__).parents[1] / 'rpc/api_server/ui/installed/'
# First make sure the assets are removed.
dl_url, latest_version = get_ui_download_url()
dl_url, latest_version = get_ui_download_url(args.get('ui_version'))
curr_version = read_ui_version(dest_folder)
if curr_version == latest_version and not args.get('erase_ui_only'):

View File

@@ -1,13 +1,14 @@
import logging
from operator import itemgetter
from typing import Any, Dict, List
from typing import Any, Dict
from colorama import init as colorama_init
from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.btanalysis import get_latest_hyperopt_file
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.state import RunMode
from freqtrade.optimize.optimize_reports import show_backtest_result
logger = logging.getLogger(__name__)
@@ -17,7 +18,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
"""
List hyperopt epochs previously evaluated
"""
from freqtrade.optimize.hyperopt import Hyperopt
from freqtrade.optimize.hyperopt_tools import HyperoptTools
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
@@ -27,49 +28,32 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
no_details = config.get('hyperopt_list_no_details', False)
no_header = False
filteroptions = {
'only_best': config.get('hyperopt_list_best', False),
'only_profitable': config.get('hyperopt_list_profitable', False),
'filter_min_trades': config.get('hyperopt_list_min_trades', 0),
'filter_max_trades': config.get('hyperopt_list_max_trades', 0),
'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None),
'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None),
'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None),
'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None),
'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None),
'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None),
'filter_min_objective': config.get('hyperopt_list_min_objective', None),
'filter_max_objective': config.get('hyperopt_list_max_objective', None),
}
results_file = get_latest_hyperopt_file(
config['user_data_dir'] / 'hyperopt_results',
config.get('hyperoptexportfilename'))
# Previous evaluations
epochs = Hyperopt.load_previous_results(results_file)
total_epochs = len(epochs)
epochs = hyperopt_filter_epochs(epochs, filteroptions)
epochs, total_epochs = HyperoptTools.load_filtered_results(results_file, config)
if print_colorized:
colorama_init(autoreset=True)
if not export_csv:
try:
print(Hyperopt.get_result_table(config, epochs, total_epochs,
not filteroptions['only_best'], print_colorized, 0))
print(HyperoptTools.get_result_table(config, epochs, total_epochs,
not config.get('hyperopt_list_best', False),
print_colorized, 0))
except KeyboardInterrupt:
print('User interrupted..')
if epochs and not no_details:
sorted_epochs = sorted(epochs, key=itemgetter('loss'))
results = sorted_epochs[0]
Hyperopt.print_epoch_details(results, total_epochs, print_json, no_header)
HyperoptTools.show_epoch_details(results, total_epochs, print_json, no_header)
if epochs and export_csv:
Hyperopt.export_csv_file(
config, epochs, total_epochs, not filteroptions['only_best'], export_csv
HyperoptTools.export_csv_file(
config, epochs, export_csv
)
@@ -77,7 +61,7 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
"""
Show details of a hyperopt epoch previously evaluated
"""
from freqtrade.optimize.hyperopt import Hyperopt
from freqtrade.optimize.hyperopt_tools import HyperoptTools
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
@@ -89,26 +73,9 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
n = config.get('hyperopt_show_index', -1)
filteroptions = {
'only_best': config.get('hyperopt_list_best', False),
'only_profitable': config.get('hyperopt_list_profitable', False),
'filter_min_trades': config.get('hyperopt_list_min_trades', 0),
'filter_max_trades': config.get('hyperopt_list_max_trades', 0),
'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None),
'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None),
'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None),
'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None),
'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None),
'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None),
'filter_min_objective': config.get('hyperopt_list_min_objective', None),
'filter_max_objective': config.get('hyperopt_list_max_objective', None)
}
# Previous evaluations
epochs = Hyperopt.load_previous_results(results_file)
total_epochs = len(epochs)
epochs, total_epochs = HyperoptTools.load_filtered_results(results_file, config)
epochs = hyperopt_filter_epochs(epochs, filteroptions)
filtered_epochs = len(epochs)
if n > filtered_epochs:
@@ -124,105 +91,14 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
if epochs:
val = epochs[n]
Hyperopt.print_epoch_details(val, total_epochs, print_json, no_header,
header_str="Epoch details")
metrics = val['results_metrics']
if 'strategy_name' in metrics:
strategy_name = metrics['strategy_name']
show_backtest_result(strategy_name, metrics,
metrics['stake_currency'])
def hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List:
"""
Filter our items from the list of hyperopt results
"""
if filteroptions['only_best']:
epochs = [x for x in epochs if x['is_best']]
if filteroptions['only_profitable']:
epochs = [x for x in epochs if x['results_metrics']['profit'] > 0]
HyperoptTools.try_export_params(config, strategy_name, val)
epochs = _hyperopt_filter_epochs_trade_count(epochs, filteroptions)
epochs = _hyperopt_filter_epochs_duration(epochs, filteroptions)
epochs = _hyperopt_filter_epochs_profit(epochs, filteroptions)
epochs = _hyperopt_filter_epochs_objective(epochs, filteroptions)
logger.info(f"{len(epochs)} " +
("best " if filteroptions['only_best'] else "") +
("profitable " if filteroptions['only_profitable'] else "") +
"epochs found.")
return epochs
def _hyperopt_filter_epochs_trade_count(epochs: List, filteroptions: dict) -> List:
if filteroptions['filter_min_trades'] > 0:
epochs = [
x for x in epochs
if x['results_metrics']['trade_count'] > filteroptions['filter_min_trades']
]
if filteroptions['filter_max_trades'] > 0:
epochs = [
x for x in epochs
if x['results_metrics']['trade_count'] < filteroptions['filter_max_trades']
]
return epochs
def _hyperopt_filter_epochs_duration(epochs: List, filteroptions: dict) -> List:
if filteroptions['filter_min_avg_time'] is not None:
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [
x for x in epochs
if x['results_metrics']['duration'] > filteroptions['filter_min_avg_time']
]
if filteroptions['filter_max_avg_time'] is not None:
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [
x for x in epochs
if x['results_metrics']['duration'] < filteroptions['filter_max_avg_time']
]
return epochs
def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List:
if filteroptions['filter_min_avg_profit'] is not None:
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [
x for x in epochs
if x['results_metrics']['avg_profit'] > filteroptions['filter_min_avg_profit']
]
if filteroptions['filter_max_avg_profit'] is not None:
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [
x for x in epochs
if x['results_metrics']['avg_profit'] < filteroptions['filter_max_avg_profit']
]
if filteroptions['filter_min_total_profit'] is not None:
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [
x for x in epochs
if x['results_metrics']['profit'] > filteroptions['filter_min_total_profit']
]
if filteroptions['filter_max_total_profit'] is not None:
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [
x for x in epochs
if x['results_metrics']['profit'] < filteroptions['filter_max_total_profit']
]
return epochs
def _hyperopt_filter_epochs_objective(epochs: List, filteroptions: dict) -> List:
if filteroptions['filter_min_objective'] is not None:
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [x for x in epochs if x['loss'] < filteroptions['filter_min_objective']]
if filteroptions['filter_max_objective'] is not None:
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [x for x in epochs if x['loss'] > filteroptions['filter_max_objective']]
return epochs
HyperoptTools.show_epoch_details(val, total_epochs, print_json, no_header,
header_str="Epoch details")

View File

@@ -1,7 +1,6 @@
import csv
import logging
import sys
from collections import OrderedDict
from pathlib import Path
from typing import Any, Dict, List
@@ -11,12 +10,12 @@ from colorama import init as colorama_init
from tabulate import tabulate
from freqtrade.configuration import setup_utils_configuration
from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGIES
from freqtrade.constants import USERPATH_STRATEGIES
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import available_exchanges, ccxt_exchanges, market_is_active
from freqtrade.misc import plural
from freqtrade.exchange import market_is_active, validate_exchanges
from freqtrade.misc import parse_db_uri_for_logging, plural
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@@ -28,14 +27,18 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
:param args: Cli args from Arguments()
:return: None
"""
exchanges = ccxt_exchanges() if args['list_exchanges_all'] else available_exchanges()
exchanges = validate_exchanges(args['list_exchanges_all'])
if args['print_one_column']:
print('\n'.join(exchanges))
print('\n'.join([e[0] for e in exchanges]))
else:
if args['list_exchanges_all']:
print(f"All exchanges supported by the ccxt library: {', '.join(exchanges)}")
print("All exchanges supported by the ccxt library:")
else:
print(f"Exchanges available for Freqtrade: {', '.join(exchanges)}")
print("Exchanges available for Freqtrade:")
exchanges = [e for e in exchanges if e[1] is not False]
print(tabulate(exchanges, headers=['Exchange name', 'Valid', 'reason']))
def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
@@ -50,15 +53,21 @@ def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
reset = ''
names = [s['name'] for s in objs]
objss_to_print = [{
objs_to_print = [{
'name': s['name'] if s['name'] else "--",
'location': s['location'].name,
'status': (red + "LOAD FAILED" + reset if s['class'] is None
else "OK" if names.count(s['name']) == 1
else yellow + "DUPLICATE NAME" + reset)
} for s in objs]
print(tabulate(objss_to_print, headers='keys', tablefmt='psql', stralign='right'))
for idx, s in enumerate(objs):
if 'hyperoptable' in s:
objs_to_print[idx].update({
'hyperoptable': "Yes" if s['hyperoptable']['count'] > 0 else "No",
'buy-Params': len(s['hyperoptable'].get('buy', [])),
'sell-Params': len(s['hyperoptable'].get('sell', [])),
})
print(tabulate(objs_to_print, headers='keys', tablefmt='psql', stralign='right'))
def start_list_strategies(args: Dict[str, Any]) -> None:
@@ -71,6 +80,11 @@ def start_list_strategies(args: Dict[str, Any]) -> None:
strategy_objs = StrategyResolver.search_all_objects(directory, not args['print_one_column'])
# Sort alphabetically
strategy_objs = sorted(strategy_objs, key=lambda x: x['name'])
for obj in strategy_objs:
if obj['class']:
obj['hyperoptable'] = obj['class'].detect_all_parameters()
else:
obj['hyperoptable'] = {'count': 0}
if args['print_one_column']:
print('\n'.join([s['name'] for s in strategy_objs]))
@@ -78,28 +92,9 @@ def start_list_strategies(args: Dict[str, Any]) -> None:
_print_objs_tabular(strategy_objs, config.get('print_colorized', False))
def start_list_hyperopts(args: Dict[str, Any]) -> None:
"""
Print files with HyperOpt custom classes available in the directory
"""
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
directory = Path(config.get('hyperopt_path', config['user_data_dir'] / USERPATH_HYPEROPTS))
hyperopt_objs = HyperOptResolver.search_all_objects(directory, not args['print_one_column'])
# Sort alphabetically
hyperopt_objs = sorted(hyperopt_objs, key=lambda x: x['name'])
if args['print_one_column']:
print('\n'.join([s['name'] for s in hyperopt_objs]))
else:
_print_objs_tabular(hyperopt_objs, config.get('print_colorized', False))
def start_list_timeframes(args: Dict[str, Any]) -> None:
"""
Print ticker intervals (timeframes) available on Exchange
Print timeframes available on Exchange
"""
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
# Do not use timeframe set in the config
@@ -139,7 +134,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
pairs_only=pairs_only,
active_only=active_only)
# Sort the pairs/markets by symbol
pairs = OrderedDict(sorted(pairs.items()))
pairs = dict(sorted(pairs.items()))
except Exception as e:
raise OperationalException(f"Cannot get markets. Reason: {e}") from e
@@ -177,7 +172,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
# human-readable formats.
print()
if len(pairs):
if pairs:
if args.get('print_list', False):
# print data as a list, with human-readable summary
print(f"{summary_str}: {', '.join(pairs.keys())}.")
@@ -211,7 +206,7 @@ def start_show_trades(args: Dict[str, Any]) -> None:
if 'db_url' not in config:
raise OperationalException("--db-url is required for this command.")
logger.info(f'Using DB: "{config["db_url"]}"')
logger.info(f'Using DB: "{parse_db_uri_for_logging(config["db_url"])}"')
init_db(config['db_url'], clean_open_orders=False)
tfilter = []

View File

@@ -3,9 +3,9 @@ from typing import Any, Dict
from freqtrade import constants
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.misc import round_coin_value
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@@ -15,6 +15,7 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[
"""
Prepare the configuration for the Hyperopt module
:param args: Cli args from Arguments()
:param method: Bot running mode
:return: Configuration
"""
config = setup_utils_configuration(args, method)

View File

@@ -4,8 +4,8 @@ from typing import Any, Dict
import rapidjson
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.resolvers import ExchangeResolver
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@@ -31,7 +31,7 @@ def start_test_pairlist(args: Dict[str, Any]) -> None:
results[curr] = pairlists.whitelist
for curr, pairlist in results.items():
if not args.get('print_one_column', False):
if not args.get('print_one_column', False) and not args.get('list_pairs_print_json', False):
print(f"Pairs for {curr}: ")
if args.get('print_one_column', False):

View File

@@ -1,8 +1,8 @@
from typing import Any, Dict
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.state import RunMode
def validate_plot_args(args: Dict[str, Any]) -> None:

View File

@@ -0,0 +1,15 @@
from typing import Any, Dict
from freqtrade.enums import RunMode
def start_webserver(args: Dict[str, Any]) -> None:
"""
Main entry point for webserver mode
"""
from freqtrade.configuration import Configuration
from freqtrade.rpc.api_server import ApiServer
# Initialize configuration
config = Configuration(args, RunMode.WEBSERVER).get_config()
ApiServer(config, standalone=True)