Merge branch 'develop' into align_userdata
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
from freqtrade.configuration.arguments import Arguments, TimeRange # noqa: F401
|
||||
from freqtrade.configuration.arguments import Arguments # noqa: F401
|
||||
from freqtrade.configuration.timerange import TimeRange # noqa: F401
|
||||
from freqtrade.configuration.configuration import Configuration # noqa: F401
|
||||
|
@@ -2,10 +2,8 @@
|
||||
This module contains the argument manager class
|
||||
"""
|
||||
import argparse
|
||||
import re
|
||||
from typing import List, NamedTuple, Optional
|
||||
from typing import List, Optional
|
||||
|
||||
import arrow
|
||||
from freqtrade.configuration.cli_options import AVAILABLE_CLI_OPTIONS
|
||||
from freqtrade import constants
|
||||
|
||||
@@ -23,7 +21,8 @@ ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_pos
|
||||
|
||||
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
|
||||
"position_stacking", "epochs", "spaces",
|
||||
"use_max_market_positions", "print_all", "hyperopt_jobs",
|
||||
"use_max_market_positions", "print_all",
|
||||
"print_colorized", "print_json", "hyperopt_jobs",
|
||||
"hyperopt_random_state", "hyperopt_min_trades",
|
||||
"hyperopt_continue", "hyperopt_loss"]
|
||||
|
||||
@@ -44,18 +43,6 @@ ARGS_PLOT_PROFIT = (ARGS_COMMON + ARGS_STRATEGY +
|
||||
["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source"])
|
||||
|
||||
|
||||
class TimeRange(NamedTuple):
|
||||
"""
|
||||
NamedTuple defining timerange inputs.
|
||||
[start/stop]type defines if [start/stop]ts shall be used.
|
||||
if *type is None, don't use corresponding startvalue.
|
||||
"""
|
||||
starttype: Optional[str] = None
|
||||
stoptype: Optional[str] = None
|
||||
startts: int = 0
|
||||
stopts: int = 0
|
||||
|
||||
|
||||
class Arguments(object):
|
||||
"""
|
||||
Arguments Class. Manage the arguments received by the cli
|
||||
@@ -138,45 +125,3 @@ class Arguments(object):
|
||||
)
|
||||
list_exchanges_cmd.set_defaults(func=start_list_exchanges)
|
||||
self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd)
|
||||
|
||||
@staticmethod
|
||||
def parse_timerange(text: Optional[str]) -> TimeRange:
|
||||
"""
|
||||
Parse the value of the argument --timerange to determine what is the range desired
|
||||
:param text: value from --timerange
|
||||
:return: Start and End range period
|
||||
"""
|
||||
if text is None:
|
||||
return TimeRange(None, None, 0, 0)
|
||||
syntax = [(r'^-(\d{8})$', (None, 'date')),
|
||||
(r'^(\d{8})-$', ('date', None)),
|
||||
(r'^(\d{8})-(\d{8})$', ('date', 'date')),
|
||||
(r'^-(\d{10})$', (None, 'date')),
|
||||
(r'^(\d{10})-$', ('date', None)),
|
||||
(r'^(\d{10})-(\d{10})$', ('date', 'date')),
|
||||
(r'^(-\d+)$', (None, 'line')),
|
||||
(r'^(\d+)-$', ('line', None)),
|
||||
(r'^(\d+)-(\d+)$', ('index', 'index'))]
|
||||
for rex, stype in syntax:
|
||||
# Apply the regular expression to text
|
||||
match = re.match(rex, text)
|
||||
if match: # Regex has matched
|
||||
rvals = match.groups()
|
||||
index = 0
|
||||
start: int = 0
|
||||
stop: int = 0
|
||||
if stype[0]:
|
||||
starts = rvals[index]
|
||||
if stype[0] == 'date' and len(starts) == 8:
|
||||
start = arrow.get(starts, 'YYYYMMDD').timestamp
|
||||
else:
|
||||
start = int(starts)
|
||||
index += 1
|
||||
if stype[1]:
|
||||
stops = rvals[index]
|
||||
if stype[1] == 'date' and len(stops) == 8:
|
||||
stop = arrow.get(stops, 'YYYYMMDD').timestamp
|
||||
else:
|
||||
stop = int(stops)
|
||||
return TimeRange(stype[0], stype[1], start, stop)
|
||||
raise Exception('Incorrect syntax for timerange "%s"' % text)
|
||||
|
@@ -2,9 +2,9 @@ import logging
|
||||
from typing import Any, Dict
|
||||
|
||||
from freqtrade import OperationalException
|
||||
from freqtrade.exchange import (is_exchange_bad, is_exchange_available,
|
||||
is_exchange_officially_supported, available_exchanges)
|
||||
|
||||
from freqtrade.exchange import (available_exchanges, get_exchange_bad_reason,
|
||||
is_exchange_available, is_exchange_bad,
|
||||
is_exchange_officially_supported)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -31,9 +31,8 @@ def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool:
|
||||
)
|
||||
|
||||
if check_for_bad and is_exchange_bad(exchange):
|
||||
logger.warning(f'Exchange "{exchange}" is known to not work with the bot yet. '
|
||||
f'Use it only for development and testing purposes.')
|
||||
return False
|
||||
raise OperationalException(f'Exchange "{exchange}" is known to not work with the bot yet. '
|
||||
f'Reason: {get_exchange_bad_reason(exchange)}')
|
||||
|
||||
if is_exchange_officially_supported(exchange):
|
||||
logger.info(f'Exchange "{exchange}" is officially supported '
|
||||
|
@@ -196,6 +196,19 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
action='store_true',
|
||||
default=False,
|
||||
),
|
||||
"print_colorized": Arg(
|
||||
'--no-color',
|
||||
help='Disable colorization of hyperopt results. May be useful if you are '
|
||||
'redirecting output to a file.',
|
||||
action='store_false',
|
||||
default=True,
|
||||
),
|
||||
"print_json": Arg(
|
||||
'--print-json',
|
||||
help='Print best result detailization in JSON format.',
|
||||
action='store_true',
|
||||
default=False,
|
||||
),
|
||||
"hyperopt_jobs": Arg(
|
||||
'-j', '--job-workers',
|
||||
help='The number of concurrently running jobs for hyperoptimization '
|
||||
@@ -231,7 +244,9 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
'--hyperopt-loss',
|
||||
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. (default: `%(default)s`).',
|
||||
'since the target for optimization is different. Built-in Hyperopt-loss-functions are: '
|
||||
'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss.'
|
||||
'(default: `%(default)s`).',
|
||||
metavar='NAME',
|
||||
default=constants.DEFAULT_HYPEROPT_LOSS,
|
||||
),
|
||||
|
@@ -5,11 +5,12 @@ import logging
|
||||
import warnings
|
||||
from argparse import Namespace
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from freqtrade import OperationalException, constants
|
||||
from freqtrade.configuration.check_exchange import check_exchange
|
||||
from freqtrade.configuration.directory_operations import create_datadir, create_userdata_dir
|
||||
from freqtrade.configuration.directory_operations import (create_datadir,
|
||||
create_userdata_dir)
|
||||
from freqtrade.configuration.json_schema import validate_config_schema
|
||||
from freqtrade.configuration.load_config import load_config_file
|
||||
from freqtrade.loggers import setup_logging
|
||||
@@ -40,43 +41,43 @@ class Configuration(object):
|
||||
|
||||
return self.config
|
||||
|
||||
def _load_config_files(self) -> Dict[str, Any]:
|
||||
@staticmethod
|
||||
def from_files(files: List[str]) -> Dict[str, Any]:
|
||||
"""
|
||||
Iterate through the config files passed in the args,
|
||||
loading all of them and merging their contents.
|
||||
Iterate through the config files passed in, loading all of them
|
||||
and merging their contents.
|
||||
Files are loaded in sequence, parameters in later configuration files
|
||||
override the same parameter from an earlier file (last definition wins).
|
||||
:param files: List of file paths
|
||||
:return: configuration dictionary
|
||||
"""
|
||||
# Keep this method as staticmethod, so it can be used from interactive environments
|
||||
config: Dict[str, Any] = {}
|
||||
|
||||
# We expect here a list of config filenames
|
||||
for path in self.args.config:
|
||||
logger.info('Using config: %s ...', path)
|
||||
for path in files:
|
||||
logger.info(f'Using config: {path} ...')
|
||||
|
||||
# Merge config options, overwriting old values
|
||||
config = deep_merge_dicts(load_config_file(path), config)
|
||||
|
||||
return config
|
||||
|
||||
def _normalize_config(self, config: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Make config more canonical -- i.e. for example add missing parts that we expect
|
||||
to be normally in it...
|
||||
"""
|
||||
# Normalize config
|
||||
if 'internals' not in config:
|
||||
config['internals'] = {}
|
||||
|
||||
# validate configuration before returning
|
||||
logger.info('Validating configuration ...')
|
||||
validate_config_schema(config)
|
||||
|
||||
return config
|
||||
|
||||
def load_config(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Extract information for sys.argv and load the bot configuration
|
||||
:return: Configuration dictionary
|
||||
"""
|
||||
# Load all configs
|
||||
config: Dict[str, Any] = self._load_config_files()
|
||||
|
||||
# Make resulting config more canonical
|
||||
self._normalize_config(config)
|
||||
|
||||
logger.info('Validating configuration ...')
|
||||
validate_config_schema(config)
|
||||
config: Dict[str, Any] = Configuration.from_files(self.args.config)
|
||||
|
||||
self._validate_config_consistency(config)
|
||||
|
||||
@@ -146,7 +147,7 @@ class Configuration(object):
|
||||
config['internals'].update({'sd_notify': True})
|
||||
|
||||
# Check if the exchange set by the user is supported
|
||||
check_exchange(config)
|
||||
check_exchange(config, config.get('experimental', {}).get('block_bad_exchanges', True))
|
||||
|
||||
def _process_datadir_options(self, config: Dict[str, Any]) -> None:
|
||||
"""
|
||||
@@ -244,6 +245,15 @@ class Configuration(object):
|
||||
self._args_to_config(config, argname='print_all',
|
||||
logstring='Parameter --print-all detected ...')
|
||||
|
||||
if 'print_colorized' in self.args and not self.args.print_colorized:
|
||||
logger.info('Parameter --no-color detected ...')
|
||||
config.update({'print_colorized': False})
|
||||
else:
|
||||
config.update({'print_colorized': True})
|
||||
|
||||
self._args_to_config(config, argname='print_json',
|
||||
logstring='Parameter --print-json detected ...')
|
||||
|
||||
self._args_to_config(config, argname='hyperopt_jobs',
|
||||
logstring='Parameter -j/--job-workers detected: {}')
|
||||
|
||||
@@ -280,7 +290,7 @@ class Configuration(object):
|
||||
if not self.runmode:
|
||||
# Handle real mode, infer dry/live from config
|
||||
self.runmode = RunMode.DRY_RUN if config.get('dry_run', True) else RunMode.LIVE
|
||||
logger.info("Runmode set to {self.runmode}.")
|
||||
logger.info(f"Runmode set to {self.runmode}.")
|
||||
|
||||
config.update({'runmode': self.runmode})
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
This module contain functions to load the configuration file
|
||||
"""
|
||||
import json
|
||||
import rapidjson
|
||||
import logging
|
||||
import sys
|
||||
from typing import Any, Dict
|
||||
@@ -12,6 +12,9 @@ from freqtrade import OperationalException
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CONFIG_PARSE_MODE = rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS
|
||||
|
||||
|
||||
def load_config_file(path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Loads a config file from the given path
|
||||
@@ -21,7 +24,7 @@ def load_config_file(path: str) -> Dict[str, Any]:
|
||||
try:
|
||||
# Read config from stdin if requested in the options
|
||||
with open(path) if path != '-' else sys.stdin as file:
|
||||
config = json.load(file)
|
||||
config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE)
|
||||
except FileNotFoundError:
|
||||
raise OperationalException(
|
||||
f'Config file "{path}" not found!'
|
||||
|
70
freqtrade/configuration/timerange.py
Normal file
70
freqtrade/configuration/timerange.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
This module contains the argument manager class
|
||||
"""
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
import arrow
|
||||
|
||||
|
||||
class TimeRange():
|
||||
"""
|
||||
object defining timerange inputs.
|
||||
[start/stop]type defines if [start/stop]ts shall be used.
|
||||
if *type is None, don't use corresponding startvalue.
|
||||
"""
|
||||
|
||||
def __init__(self, starttype: Optional[str] = None, stoptype: Optional[str] = None,
|
||||
startts: int = 0, stopts: int = 0):
|
||||
|
||||
self.starttype: Optional[str] = starttype
|
||||
self.stoptype: Optional[str] = stoptype
|
||||
self.startts: int = startts
|
||||
self.stopts: int = stopts
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Override the default Equals behavior"""
|
||||
return (self.starttype == other.starttype and self.stoptype == other.stoptype
|
||||
and self.startts == other.startts and self.stopts == other.stopts)
|
||||
|
||||
@staticmethod
|
||||
def parse_timerange(text: Optional[str]):
|
||||
"""
|
||||
Parse the value of the argument --timerange to determine what is the range desired
|
||||
:param text: value from --timerange
|
||||
:return: Start and End range period
|
||||
"""
|
||||
if text is None:
|
||||
return TimeRange(None, None, 0, 0)
|
||||
syntax = [(r'^-(\d{8})$', (None, 'date')),
|
||||
(r'^(\d{8})-$', ('date', None)),
|
||||
(r'^(\d{8})-(\d{8})$', ('date', 'date')),
|
||||
(r'^-(\d{10})$', (None, 'date')),
|
||||
(r'^(\d{10})-$', ('date', None)),
|
||||
(r'^(\d{10})-(\d{10})$', ('date', 'date')),
|
||||
(r'^(-\d+)$', (None, 'line')),
|
||||
(r'^(\d+)-$', ('line', None)),
|
||||
(r'^(\d+)-(\d+)$', ('index', 'index'))]
|
||||
for rex, stype in syntax:
|
||||
# Apply the regular expression to text
|
||||
match = re.match(rex, text)
|
||||
if match: # Regex has matched
|
||||
rvals = match.groups()
|
||||
index = 0
|
||||
start: int = 0
|
||||
stop: int = 0
|
||||
if stype[0]:
|
||||
starts = rvals[index]
|
||||
if stype[0] == 'date' and len(starts) == 8:
|
||||
start = arrow.get(starts, 'YYYYMMDD').timestamp
|
||||
else:
|
||||
start = int(starts)
|
||||
index += 1
|
||||
if stype[1]:
|
||||
stops = rvals[index]
|
||||
if stype[1] == 'date' and len(stops) == 8:
|
||||
stop = arrow.get(stops, 'YYYYMMDD').timestamp
|
||||
else:
|
||||
stop = int(stops)
|
||||
return TimeRange(stype[0], stype[1], start, stop)
|
||||
raise Exception('Incorrect syntax for timerange "%s"' % text)
|
Reference in New Issue
Block a user