Merge pull request #9975 from freqtrade/feat/configError
exception ConfigurationError
This commit is contained in:
@@ -129,6 +129,8 @@ Below is an outline of exception inheritance hierarchy:
|
|||||||
+ FreqtradeException
|
+ FreqtradeException
|
||||||
|
|
|
|
||||||
+---+ OperationalException
|
+---+ OperationalException
|
||||||
|
| |
|
||||||
|
| +---+ ConfigurationError
|
||||||
|
|
|
|
||||||
+---+ DependencyException
|
+---+ DependencyException
|
||||||
| |
|
| |
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import Any, Dict
|
|||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -34,9 +34,9 @@ def setup_analyze_configuration(args: Dict[str, Any], method: RunMode) -> Dict[s
|
|||||||
btfile = Path(config['exportfilename'])
|
btfile = Path(config['exportfilename'])
|
||||||
signals_file = f"{btfile.parent}/{btfile.stem}_signals.pkl"
|
signals_file = f"{btfile.parent}/{btfile.stem}_signals.pkl"
|
||||||
else:
|
else:
|
||||||
raise OperationalException(f"{config['exportfilename']} does not exist.")
|
raise ConfigurationError(f"{config['exportfilename']} does not exist.")
|
||||||
else:
|
else:
|
||||||
raise OperationalException('exportfilename not in config.')
|
raise ConfigurationError('exportfilename not in config.')
|
||||||
|
|
||||||
if (not Path(signals_file).exists()):
|
if (not Path(signals_file).exists()):
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_forma
|
|||||||
convert_trades_to_ohlcv)
|
convert_trades_to_ohlcv)
|
||||||
from freqtrade.data.history import download_data_main
|
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 OperationalException
|
from freqtrade.exceptions import ConfigurationError
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
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.resolvers import ExchangeResolver
|
||||||
@@ -21,11 +21,11 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def _check_data_config_download_sanity(config: Config) -> None:
|
def _check_data_config_download_sanity(config: Config) -> None:
|
||||||
if 'days' in config and 'timerange' in config:
|
if 'days' in config and 'timerange' in config:
|
||||||
raise OperationalException("--days and --timerange are mutually exclusive. "
|
raise ConfigurationError("--days and --timerange are mutually exclusive. "
|
||||||
"You can only specify one or the other.")
|
"You can only specify one or the other.")
|
||||||
|
|
||||||
if 'pairs' not in config:
|
if 'pairs' not in config:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Downloading data requires a list of pairs. "
|
"Downloading data requires a list of pairs. "
|
||||||
"Please check the documentation on how to configure this.")
|
"Please check the documentation on how to configure this.")
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from freqtrade.configuration import setup_utils_configuration
|
|||||||
from freqtrade.configuration.directory_operations import copy_sample_files, create_userdata_dir
|
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 OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
from freqtrade.util import render_template, render_template_with_fallback
|
from freqtrade.util import render_template, render_template_with_fallback
|
||||||
|
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
|
|||||||
deploy_new_strategy(args['strategy'], new_path, args['template'])
|
deploy_new_strategy(args['strategy'], new_path, args['template'])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise OperationalException("`new-strategy` requires --strategy to be set.")
|
raise ConfigurationError("`new-strategy` requires --strategy to be set.")
|
||||||
|
|
||||||
|
|
||||||
def clean_ui_subdir(directory: Path):
|
def clean_ui_subdir(directory: Path):
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from tabulate import tabulate
|
|||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
from freqtrade.exchange import list_available_exchanges, market_is_active
|
from freqtrade.exchange import list_available_exchanges, market_is_active
|
||||||
from freqtrade.misc import parse_db_uri_for_logging, plural
|
from freqtrade.misc import parse_db_uri_for_logging, plural
|
||||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||||
@@ -246,7 +246,7 @@ def start_show_trades(args: Dict[str, Any]) -> None:
|
|||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
if 'db_url' not in config:
|
if 'db_url' not in config:
|
||||||
raise OperationalException("--db-url is required for this command.")
|
raise ConfigurationError("--db-url is required for this command.")
|
||||||
|
|
||||||
logger.info(f'Using DB: "{parse_db_uri_for_logging(config["db_url"])}"')
|
logger.info(f'Using DB: "{parse_db_uri_for_logging(config["db_url"])}"')
|
||||||
init_db(config['db_url'])
|
init_db(config['db_url'])
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import Any, Dict
|
|||||||
from freqtrade import constants
|
from freqtrade import constants
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
from freqtrade.util import fmt_coin
|
from freqtrade.util import fmt_coin
|
||||||
|
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[
|
|||||||
and config['stake_amount'] > wallet_size):
|
and config['stake_amount'] > wallet_size):
|
||||||
wallet = fmt_coin(wallet_size, config['stake_currency'])
|
wallet = fmt_coin(wallet_size, config['stake_currency'])
|
||||||
stake = fmt_coin(config['stake_amount'], config['stake_currency'])
|
stake = fmt_coin(config['stake_amount'], config['stake_currency'])
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f"Starting balance ({wallet}) is smaller than stake_amount {stake}. "
|
f"Starting balance ({wallet}) is smaller than stake_amount {stake}. "
|
||||||
f"Wallet is calculated as `dry_run_wallet * tradable_balance_ratio`."
|
f"Wallet is calculated as `dry_run_wallet * tradable_balance_ratio`."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ from typing import Any, Dict
|
|||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError
|
||||||
|
|
||||||
|
|
||||||
def validate_plot_args(args: Dict[str, Any]) -> None:
|
def validate_plot_args(args: Dict[str, Any]) -> None:
|
||||||
if not args.get('datadir') and not args.get('config'):
|
if not args.get('datadir') and not args.get('config'):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"You need to specify either `--datadir` or `--config` "
|
"You need to specify either `--datadir` or `--config` "
|
||||||
"for plot-profit and plot-dataframe.")
|
"for plot-profit and plot-dataframe.")
|
||||||
|
|
||||||
|
|||||||
@@ -23,11 +23,6 @@ def start_trading(args: Dict[str, Any]) -> int:
|
|||||||
signal.signal(signal.SIGTERM, term_handler)
|
signal.signal(signal.SIGTERM, term_handler)
|
||||||
worker = Worker(args)
|
worker = Worker(args)
|
||||||
worker.run()
|
worker.run()
|
||||||
except Exception as e:
|
|
||||||
logger.error(str(e))
|
|
||||||
logger.exception("Fatal exception!")
|
|
||||||
except (KeyboardInterrupt):
|
|
||||||
logger.info('SIGINT received, aborting ...')
|
|
||||||
finally:
|
finally:
|
||||||
if worker:
|
if worker:
|
||||||
logger.info("worker found ... calling exit")
|
logger.info("worker found ... calling exit")
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from jsonschema.exceptions import ValidationError, best_match
|
|||||||
from freqtrade import constants
|
from freqtrade import constants
|
||||||
from freqtrade.configuration.deprecated_settings import process_deprecated_setting
|
from freqtrade.configuration.deprecated_settings import process_deprecated_setting
|
||||||
from freqtrade.enums import RunMode, TradingMode
|
from freqtrade.enums import RunMode, TradingMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -73,7 +73,7 @@ def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = Fal
|
|||||||
Should be ran after loading both configuration and strategy,
|
Should be ran after loading both configuration and strategy,
|
||||||
since strategies can set certain configuration settings too.
|
since strategies can set certain configuration settings too.
|
||||||
:param conf: Config in JSON format
|
:param conf: Config in JSON format
|
||||||
:return: Returns None if everything is ok, otherwise throw an OperationalException
|
:return: Returns None if everything is ok, otherwise throw an ConfigurationError
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# validating trailing stoploss
|
# validating trailing stoploss
|
||||||
@@ -98,12 +98,12 @@ def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = Fal
|
|||||||
def _validate_unlimited_amount(conf: Dict[str, Any]) -> None:
|
def _validate_unlimited_amount(conf: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
If edge is disabled, either max_open_trades or stake_amount need to be set.
|
If edge is disabled, either max_open_trades or stake_amount need to be set.
|
||||||
:raise: OperationalException if config validation failed
|
:raise: ConfigurationError if config validation failed
|
||||||
"""
|
"""
|
||||||
if (not conf.get('edge', {}).get('enabled')
|
if (not conf.get('edge', {}).get('enabled')
|
||||||
and conf.get('max_open_trades') == float('inf')
|
and conf.get('max_open_trades') == float('inf')
|
||||||
and conf.get('stake_amount') == constants.UNLIMITED_STAKE_AMOUNT):
|
and conf.get('stake_amount') == constants.UNLIMITED_STAKE_AMOUNT):
|
||||||
raise OperationalException("`max_open_trades` and `stake_amount` cannot both be unlimited.")
|
raise ConfigurationError("`max_open_trades` and `stake_amount` cannot both be unlimited.")
|
||||||
|
|
||||||
|
|
||||||
def _validate_price_config(conf: Dict[str, Any]) -> None:
|
def _validate_price_config(conf: Dict[str, Any]) -> None:
|
||||||
@@ -113,18 +113,18 @@ def _validate_price_config(conf: Dict[str, Any]) -> None:
|
|||||||
# TODO: The below could be an enforced setting when using market orders
|
# TODO: The below could be an enforced setting when using market orders
|
||||||
if (conf.get('order_types', {}).get('entry') == 'market'
|
if (conf.get('order_types', {}).get('entry') == 'market'
|
||||||
and conf.get('entry_pricing', {}).get('price_side') not in ('ask', 'other')):
|
and conf.get('entry_pricing', {}).get('price_side') not in ('ask', 'other')):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'Market entry orders require entry_pricing.price_side = "other".')
|
'Market entry orders require entry_pricing.price_side = "other".')
|
||||||
|
|
||||||
if (conf.get('order_types', {}).get('exit') == 'market'
|
if (conf.get('order_types', {}).get('exit') == 'market'
|
||||||
and conf.get('exit_pricing', {}).get('price_side') not in ('bid', 'other')):
|
and conf.get('exit_pricing', {}).get('price_side') not in ('bid', 'other')):
|
||||||
raise OperationalException('Market exit orders require exit_pricing.price_side = "other".')
|
raise ConfigurationError('Market exit orders require exit_pricing.price_side = "other".')
|
||||||
|
|
||||||
|
|
||||||
def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
|
def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
if conf.get('stoploss') == 0.0:
|
if conf.get('stoploss') == 0.0:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'The config stoploss needs to be different from 0 to avoid problems with sell orders.'
|
'The config stoploss needs to be different from 0 to avoid problems with sell orders.'
|
||||||
)
|
)
|
||||||
# Skip if trailing stoploss is not activated
|
# Skip if trailing stoploss is not activated
|
||||||
@@ -137,17 +137,17 @@ def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
if tsl_only_offset:
|
if tsl_only_offset:
|
||||||
if tsl_positive == 0.0:
|
if tsl_positive == 0.0:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'The config trailing_only_offset_is_reached needs '
|
'The config trailing_only_offset_is_reached needs '
|
||||||
'trailing_stop_positive_offset to be more than 0 in your config.')
|
'trailing_stop_positive_offset to be more than 0 in your config.')
|
||||||
if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive:
|
if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'The config trailing_stop_positive_offset needs '
|
'The config trailing_stop_positive_offset needs '
|
||||||
'to be greater than trailing_stop_positive in your config.')
|
'to be greater than trailing_stop_positive in your config.')
|
||||||
|
|
||||||
# Fetch again without default
|
# Fetch again without default
|
||||||
if 'trailing_stop_positive' in conf and float(conf['trailing_stop_positive']) == 0.0:
|
if 'trailing_stop_positive' in conf and float(conf['trailing_stop_positive']) == 0.0:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'The config trailing_stop_positive needs to be different from 0 '
|
'The config trailing_stop_positive needs to be different from 0 '
|
||||||
'to avoid problems with sell orders.'
|
'to avoid problems with sell orders.'
|
||||||
)
|
)
|
||||||
@@ -162,7 +162,7 @@ def _validate_edge(conf: Dict[str, Any]) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if not conf.get('use_exit_signal', True):
|
if not conf.get('use_exit_signal', True):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Edge requires `use_exit_signal` to be True, otherwise no sells will happen."
|
"Edge requires `use_exit_signal` to be True, otherwise no sells will happen."
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None:
|
|||||||
for pl in conf.get('pairlists', [{'method': 'StaticPairList'}]):
|
for pl in conf.get('pairlists', [{'method': 'StaticPairList'}]):
|
||||||
if (isinstance(pl, dict) and pl.get('method') == 'StaticPairList'
|
if (isinstance(pl, dict) and pl.get('method') == 'StaticPairList'
|
||||||
and not conf.get('exchange', {}).get('pair_whitelist')):
|
and not conf.get('exchange', {}).get('pair_whitelist')):
|
||||||
raise OperationalException("StaticPairList requires pair_whitelist to be set.")
|
raise ConfigurationError("StaticPairList requires pair_whitelist to be set.")
|
||||||
|
|
||||||
|
|
||||||
def _validate_protections(conf: Dict[str, Any]) -> None:
|
def _validate_protections(conf: Dict[str, Any]) -> None:
|
||||||
@@ -188,13 +188,13 @@ def _validate_protections(conf: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
for prot in conf.get('protections', []):
|
for prot in conf.get('protections', []):
|
||||||
if ('stop_duration' in prot and 'stop_duration_candles' in prot):
|
if ('stop_duration' in prot and 'stop_duration_candles' in prot):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Protections must specify either `stop_duration` or `stop_duration_candles`.\n"
|
"Protections must specify either `stop_duration` or `stop_duration_candles`.\n"
|
||||||
f"Please fix the protection {prot.get('method')}"
|
f"Please fix the protection {prot.get('method')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if ('lookback_period' in prot and 'lookback_period_candles' in prot):
|
if ('lookback_period' in prot and 'lookback_period_candles' in prot):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
|
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
|
||||||
f"Please fix the protection {prot.get('method')}"
|
f"Please fix the protection {prot.get('method')}"
|
||||||
)
|
)
|
||||||
@@ -206,7 +206,7 @@ def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
|
|||||||
ob_max = ask_strategy.get('order_book_max')
|
ob_max = ask_strategy.get('order_book_max')
|
||||||
if ob_min is not None and ob_max is not None and ask_strategy.get('use_order_book'):
|
if ob_min is not None and ob_max is not None and ask_strategy.get('use_order_book'):
|
||||||
if ob_min != ob_max:
|
if ob_min != ob_max:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Using order_book_max != order_book_min in exit_pricing is no longer supported."
|
"Using order_book_max != order_book_min in exit_pricing is no longer supported."
|
||||||
"Please pick one value and use `order_book_top` in the future."
|
"Please pick one value and use `order_book_top` in the future."
|
||||||
)
|
)
|
||||||
@@ -234,7 +234,7 @@ def _validate_time_in_force(conf: Dict[str, Any]) -> None:
|
|||||||
time_in_force = conf.get('order_time_in_force', {})
|
time_in_force = conf.get('order_time_in_force', {})
|
||||||
if 'buy' in time_in_force or 'sell' in time_in_force:
|
if 'buy' in time_in_force or 'sell' in time_in_force:
|
||||||
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Please migrate your time_in_force settings to use 'entry' and 'exit'.")
|
"Please migrate your time_in_force settings to use 'entry' and 'exit'.")
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@@ -255,7 +255,7 @@ def _validate_order_types(conf: Dict[str, Any]) -> None:
|
|||||||
'forcesell', 'emergencyexit', 'forceexit', 'forceentry']
|
'forcesell', 'emergencyexit', 'forceexit', 'forceentry']
|
||||||
if any(x in order_types for x in old_order_types):
|
if any(x in order_types for x in old_order_types):
|
||||||
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Please migrate your order_types settings to use the new wording.")
|
"Please migrate your order_types settings to use the new wording.")
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@@ -280,7 +280,7 @@ def _validate_unfilledtimeout(conf: Dict[str, Any]) -> None:
|
|||||||
unfilledtimeout = conf.get('unfilledtimeout', {})
|
unfilledtimeout = conf.get('unfilledtimeout', {})
|
||||||
if any(x in unfilledtimeout for x in ['buy', 'sell']):
|
if any(x in unfilledtimeout for x in ['buy', 'sell']):
|
||||||
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Please migrate your unfilledtimeout settings to use the new wording.")
|
"Please migrate your unfilledtimeout settings to use the new wording.")
|
||||||
else:
|
else:
|
||||||
|
|
||||||
@@ -300,7 +300,7 @@ def _validate_pricing_rules(conf: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
if conf.get('ask_strategy') or conf.get('bid_strategy'):
|
if conf.get('ask_strategy') or conf.get('bid_strategy'):
|
||||||
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Please migrate your pricing settings to use the new wording.")
|
"Please migrate your pricing settings to use the new wording.")
|
||||||
else:
|
else:
|
||||||
|
|
||||||
@@ -331,7 +331,7 @@ def _validate_freqai_hyperopt(conf: Dict[str, Any]) -> None:
|
|||||||
freqai_enabled = conf.get('freqai', {}).get('enabled', False)
|
freqai_enabled = conf.get('freqai', {}).get('enabled', False)
|
||||||
analyze_per_epoch = conf.get('analyze_per_epoch', False)
|
analyze_per_epoch = conf.get('analyze_per_epoch', False)
|
||||||
if analyze_per_epoch and freqai_enabled:
|
if analyze_per_epoch and freqai_enabled:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'Using analyze-per-epoch parameter is not supported with a FreqAI strategy.')
|
'Using analyze-per-epoch parameter is not supported with a FreqAI strategy.')
|
||||||
|
|
||||||
|
|
||||||
@@ -350,7 +350,7 @@ def _validate_freqai_include_timeframes(conf: Dict[str, Any], preliminary: bool)
|
|||||||
if tf_s < main_tf_s:
|
if tf_s < main_tf_s:
|
||||||
offending_lines.append(tf)
|
offending_lines.append(tf)
|
||||||
if offending_lines:
|
if offending_lines:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f"Main timeframe of {main_tf} must be smaller or equal to FreqAI "
|
f"Main timeframe of {main_tf} must be smaller or equal to FreqAI "
|
||||||
f"`include_timeframes`.Offending include-timeframes: {', '.join(offending_lines)}")
|
f"`include_timeframes`.Offending include-timeframes: {', '.join(offending_lines)}")
|
||||||
|
|
||||||
@@ -368,17 +368,17 @@ def _validate_freqai_backtest(conf: Dict[str, Any]) -> None:
|
|||||||
timerange = conf.get('timerange')
|
timerange = conf.get('timerange')
|
||||||
freqai_backtest_live_models = conf.get('freqai_backtest_live_models', False)
|
freqai_backtest_live_models = conf.get('freqai_backtest_live_models', False)
|
||||||
if freqai_backtest_live_models and freqai_enabled and timerange:
|
if freqai_backtest_live_models and freqai_enabled and timerange:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'Using timerange parameter is not supported with '
|
'Using timerange parameter is not supported with '
|
||||||
'--freqai-backtest-live-models parameter.')
|
'--freqai-backtest-live-models parameter.')
|
||||||
|
|
||||||
if freqai_backtest_live_models and not freqai_enabled:
|
if freqai_backtest_live_models and not freqai_enabled:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'Using --freqai-backtest-live-models parameter is only '
|
'Using --freqai-backtest-live-models parameter is only '
|
||||||
'supported with a FreqAI strategy.')
|
'supported with a FreqAI strategy.')
|
||||||
|
|
||||||
if freqai_enabled and not freqai_backtest_live_models and not timerange:
|
if freqai_enabled and not freqai_backtest_live_models and not timerange:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'Please pass --timerange if you intend to use FreqAI for backtesting.')
|
'Please pass --timerange if you intend to use FreqAI for backtesting.')
|
||||||
|
|
||||||
|
|
||||||
@@ -386,12 +386,12 @@ def _validate_consumers(conf: Dict[str, Any]) -> None:
|
|||||||
emc_conf = conf.get('external_message_consumer', {})
|
emc_conf = conf.get('external_message_consumer', {})
|
||||||
if emc_conf.get('enabled', False):
|
if emc_conf.get('enabled', False):
|
||||||
if len(emc_conf.get('producers', [])) < 1:
|
if len(emc_conf.get('producers', [])) < 1:
|
||||||
raise OperationalException("You must specify at least 1 Producer to connect to.")
|
raise ConfigurationError("You must specify at least 1 Producer to connect to.")
|
||||||
|
|
||||||
producer_names = [p['name'] for p in emc_conf.get('producers', [])]
|
producer_names = [p['name'] for p in emc_conf.get('producers', [])]
|
||||||
duplicates = [item for item, count in Counter(producer_names).items() if count > 1]
|
duplicates = [item for item, count in Counter(producer_names).items() if count > 1]
|
||||||
if duplicates:
|
if duplicates:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f"Producer names must be unique. Duplicate: {', '.join(duplicates)}")
|
f"Producer names must be unique. Duplicate: {', '.join(duplicates)}")
|
||||||
if conf.get('process_only_new_candles', True):
|
if conf.get('process_only_new_candles', True):
|
||||||
# Warning here or require it?
|
# Warning here or require it?
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import logging
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from freqtrade.constants import Config
|
from freqtrade.constants import Config
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -41,7 +41,7 @@ def process_removed_setting(config: Config,
|
|||||||
section1_config = config.get(section1, {})
|
section1_config = config.get(section1, {})
|
||||||
if name1 in section1_config:
|
if name1 in section1_config:
|
||||||
section_2 = f"{section2}.{name2}" if section2 else f"{name2}"
|
section_2 = f"{section2}.{name2}" if section2 else f"{name2}"
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f"Setting `{section1}.{name1}` has been moved to `{section_2}. "
|
f"Setting `{section1}.{name1}` has been moved to `{section_2}. "
|
||||||
f"Please delete it from your configuration and use the `{section_2}` "
|
f"Please delete it from your configuration and use the `{section_2}` "
|
||||||
"setting instead."
|
"setting instead."
|
||||||
@@ -122,7 +122,7 @@ def process_temporary_deprecated_settings(config: Config) -> None:
|
|||||||
None, 'ignore_roi_if_entry_signal')
|
None, 'ignore_roi_if_entry_signal')
|
||||||
if (config.get('edge', {}).get('enabled', False)
|
if (config.get('edge', {}).get('enabled', False)
|
||||||
and 'capital_available_percentage' in config.get('edge', {})):
|
and 'capital_available_percentage' in config.get('edge', {})):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"DEPRECATED: "
|
"DEPRECATED: "
|
||||||
"Using 'edge.capital_available_percentage' has been deprecated in favor of "
|
"Using 'edge.capital_available_percentage' has been deprecated in favor of "
|
||||||
"'tradable_balance_ratio'. Please migrate your configuration to "
|
"'tradable_balance_ratio'. Please migrate your configuration to "
|
||||||
@@ -131,7 +131,7 @@ def process_temporary_deprecated_settings(config: Config) -> None:
|
|||||||
)
|
)
|
||||||
if 'ticker_interval' in config:
|
if 'ticker_interval' in config:
|
||||||
|
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"DEPRECATED: 'ticker_interval' detected. "
|
"DEPRECATED: 'ticker_interval' detected. "
|
||||||
"Please use 'timeframe' instead of 'ticker_interval."
|
"Please use 'timeframe' instead of 'ticker_interval."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from typing import Any, Dict, List, Optional
|
|||||||
import rapidjson
|
import rapidjson
|
||||||
|
|
||||||
from freqtrade.constants import MINIMAL_CONFIG, Config
|
from freqtrade.constants import MINIMAL_CONFIG, Config
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
from freqtrade.misc import deep_merge_dicts
|
from freqtrade.misc import deep_merge_dicts
|
||||||
|
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ def load_config_file(path: str) -> Dict[str, Any]:
|
|||||||
' Please create a config file or check whether it exists.') from None
|
' Please create a config file or check whether it exists.') from None
|
||||||
except rapidjson.JSONDecodeError as e:
|
except rapidjson.JSONDecodeError as e:
|
||||||
err_range = log_config_error_range(path, str(e))
|
err_range = log_config_error_range(path, str(e))
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f'{e}\n'
|
f'{e}\n'
|
||||||
f'Please verify the following segment of your configuration:\n{err_range}'
|
f'Please verify the following segment of your configuration:\n{err_range}'
|
||||||
if err_range else 'Please verify your configuration file for syntax errors.'
|
if err_range else 'Please verify your configuration file for syntax errors.'
|
||||||
@@ -83,7 +83,7 @@ def load_from_files(
|
|||||||
"""
|
"""
|
||||||
config: Config = {}
|
config: Config = {}
|
||||||
if level > 5:
|
if level > 5:
|
||||||
raise OperationalException("Config loop detected.")
|
raise ConfigurationError("Config loop detected.")
|
||||||
|
|
||||||
if not files:
|
if not files:
|
||||||
return deepcopy(MINIMAL_CONFIG)
|
return deepcopy(MINIMAL_CONFIG)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from typing import Optional
|
|||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -156,7 +156,7 @@ class TimeRange:
|
|||||||
else:
|
else:
|
||||||
stop = int(stops)
|
stop = int(stops)
|
||||||
if start > stop > 0:
|
if start > stop > 0:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f'Start date is after stop date for timerange "{text}"')
|
f'Start date is after stop date for timerange "{text}"')
|
||||||
return cls(stype[0], stype[1], start, stop)
|
return cls(stype[0], stype[1], start, stop)
|
||||||
raise OperationalException(f'Incorrect syntax for timerange "{text}"')
|
raise ConfigurationError(f'Incorrect syntax for timerange "{text}"')
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import numpy as np
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from freqtrade.constants import LAST_BT_RESULT_FN, IntOrInf
|
from freqtrade.constants import LAST_BT_RESULT_FN, IntOrInf
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
from freqtrade.misc import file_dump_json, json_load
|
from freqtrade.misc import file_dump_json, json_load
|
||||||
from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename
|
from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename
|
||||||
from freqtrade.persistence import LocalTrade, Trade, init_db
|
from freqtrade.persistence import LocalTrade, Trade, init_db
|
||||||
@@ -106,7 +106,7 @@ def get_latest_hyperopt_file(
|
|||||||
directory = Path(directory)
|
directory = Path(directory)
|
||||||
if predef_filename:
|
if predef_filename:
|
||||||
if Path(predef_filename).is_absolute():
|
if Path(predef_filename).is_absolute():
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"--hyperopt-filename expects only the filename, not an absolute path.")
|
"--hyperopt-filename expects only the filename, not an absolute path.")
|
||||||
return directory / predef_filename
|
return directory / predef_filename
|
||||||
return directory / get_latest_hyperopt_filename(directory)
|
return directory / get_latest_hyperopt_filename(directory)
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ class OperationalException(FreqtradeException):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigurationError(OperationalException):
|
||||||
|
"""
|
||||||
|
Configuration error. Usually caused by invalid configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class DependencyException(FreqtradeException):
|
class DependencyException(FreqtradeException):
|
||||||
"""
|
"""
|
||||||
Indicates that an assumed dependency is not met.
|
Indicates that an assumed dependency is not met.
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHAN
|
|||||||
ListPairsWithTimeframes, MakerTaker, OBLiteral, PairWithTimeframe)
|
ListPairsWithTimeframes, MakerTaker, OBLiteral, PairWithTimeframe)
|
||||||
from freqtrade.data.converter import clean_ohlcv_dataframe, ohlcv_to_dataframe, trades_dict_to_list
|
from freqtrade.data.converter import clean_ohlcv_dataframe, ohlcv_to_dataframe, trades_dict_to_list
|
||||||
from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, PriceType, RunMode, TradingMode
|
from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, PriceType, RunMode, TradingMode
|
||||||
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
|
from freqtrade.exceptions import (ConfigurationError, DDosProtection, ExchangeError,
|
||||||
InvalidOrderException, OperationalException, PricingError,
|
InsufficientFundsError, InvalidOrderException,
|
||||||
RetryableOrderError, TemporaryError)
|
OperationalException, PricingError, RetryableOrderError,
|
||||||
|
TemporaryError)
|
||||||
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, remove_exchange_credentials,
|
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, remove_exchange_credentials,
|
||||||
retrier, retrier_async)
|
retrier, retrier_async)
|
||||||
from freqtrade.exchange.exchange_utils import (ROUND, ROUND_DOWN, ROUND_UP, CcxtModuleType,
|
from freqtrade.exchange.exchange_utils import (ROUND, ROUND_DOWN, ROUND_UP, CcxtModuleType,
|
||||||
@@ -529,7 +530,7 @@ class Exchange:
|
|||||||
)
|
)
|
||||||
quote_currencies = self.get_quote_currencies()
|
quote_currencies = self.get_quote_currencies()
|
||||||
if stake_currency not in quote_currencies:
|
if stake_currency not in quote_currencies:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f"{stake_currency} is not available as stake on {self.name}. "
|
f"{stake_currency} is not available as stake on {self.name}. "
|
||||||
f"Available currencies are: {', '.join(quote_currencies)}")
|
f"Available currencies are: {', '.join(quote_currencies)}")
|
||||||
|
|
||||||
@@ -597,7 +598,7 @@ class Exchange:
|
|||||||
f"is therefore not supported. ccxt fetchOHLCV: {self.exchange_has('fetchOHLCV')}")
|
f"is therefore not supported. ccxt fetchOHLCV: {self.exchange_has('fetchOHLCV')}")
|
||||||
|
|
||||||
if timeframe and (timeframe not in self.timeframes):
|
if timeframe and (timeframe not in self.timeframes):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f"Invalid timeframe '{timeframe}'. This exchange supports: {self.timeframes}")
|
f"Invalid timeframe '{timeframe}'. This exchange supports: {self.timeframes}")
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -605,7 +606,7 @@ class Exchange:
|
|||||||
and self._config['runmode'] != RunMode.UTIL_EXCHANGE
|
and self._config['runmode'] != RunMode.UTIL_EXCHANGE
|
||||||
and timeframe_to_minutes(timeframe) < 1
|
and timeframe_to_minutes(timeframe) < 1
|
||||||
):
|
):
|
||||||
raise OperationalException("Timeframes < 1m are currently not supported by Freqtrade.")
|
raise ConfigurationError("Timeframes < 1m are currently not supported by Freqtrade.")
|
||||||
|
|
||||||
def validate_ordertypes(self, order_types: Dict) -> None:
|
def validate_ordertypes(self, order_types: Dict) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -613,7 +614,7 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
if any(v == 'market' for k, v in order_types.items()):
|
if any(v == 'market' for k, v in order_types.items()):
|
||||||
if not self.exchange_has('createMarketOrder'):
|
if not self.exchange_has('createMarketOrder'):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f'Exchange {self.name} does not support market orders.')
|
f'Exchange {self.name} does not support market orders.')
|
||||||
self.validate_stop_ordertypes(order_types)
|
self.validate_stop_ordertypes(order_types)
|
||||||
|
|
||||||
@@ -623,7 +624,7 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
if (order_types.get("stoploss_on_exchange")
|
if (order_types.get("stoploss_on_exchange")
|
||||||
and not self._ft_has.get("stoploss_on_exchange", False)):
|
and not self._ft_has.get("stoploss_on_exchange", False)):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f'On exchange stoploss is not supported for {self.name}.'
|
f'On exchange stoploss is not supported for {self.name}.'
|
||||||
)
|
)
|
||||||
if self.trading_mode == TradingMode.FUTURES:
|
if self.trading_mode == TradingMode.FUTURES:
|
||||||
@@ -633,17 +634,17 @@ class Exchange:
|
|||||||
and 'stoploss_price_type' in order_types
|
and 'stoploss_price_type' in order_types
|
||||||
and order_types['stoploss_price_type'] not in price_mapping
|
and order_types['stoploss_price_type'] not in price_mapping
|
||||||
):
|
):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f'On exchange stoploss price type is not supported for {self.name}.'
|
f'On exchange stoploss price type is not supported for {self.name}.'
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_pricing(self, pricing: Dict) -> None:
|
def validate_pricing(self, pricing: Dict) -> None:
|
||||||
if pricing.get('use_order_book', False) and not self.exchange_has('fetchL2OrderBook'):
|
if pricing.get('use_order_book', False) and not self.exchange_has('fetchL2OrderBook'):
|
||||||
raise OperationalException(f'Orderbook not available for {self.name}.')
|
raise ConfigurationError(f'Orderbook not available for {self.name}.')
|
||||||
if (not pricing.get('use_order_book', False) and (
|
if (not pricing.get('use_order_book', False) and (
|
||||||
not self.exchange_has('fetchTicker')
|
not self.exchange_has('fetchTicker')
|
||||||
or not self._ft_has['tickers_have_price'])):
|
or not self._ft_has['tickers_have_price'])):
|
||||||
raise OperationalException(f'Ticker pricing not available for {self.name}.')
|
raise ConfigurationError(f'Ticker pricing not available for {self.name}.')
|
||||||
|
|
||||||
def validate_order_time_in_force(self, order_time_in_force: Dict) -> None:
|
def validate_order_time_in_force(self, order_time_in_force: Dict) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -651,7 +652,7 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
if any(v.upper() not in self._ft_has["order_time_in_force"]
|
if any(v.upper() not in self._ft_has["order_time_in_force"]
|
||||||
for k, v in order_time_in_force.items()):
|
for k, v in order_time_in_force.items()):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f'Time in force policies are not supported for {self.name} yet.')
|
f'Time in force policies are not supported for {self.name} yet.')
|
||||||
|
|
||||||
def validate_required_startup_candles(self, startup_candles: int, timeframe: str) -> int:
|
def validate_required_startup_candles(self, startup_candles: int, timeframe: str) -> int:
|
||||||
@@ -673,12 +674,12 @@ class Exchange:
|
|||||||
|
|
||||||
if required_candle_call_count > 5:
|
if required_candle_call_count > 5:
|
||||||
# Only allow 5 calls per pair to somewhat limit the impact
|
# Only allow 5 calls per pair to somewhat limit the impact
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f"This strategy requires {startup_candles} candles to start, "
|
f"This strategy requires {startup_candles} candles to start, "
|
||||||
"which is more than 5x "
|
"which is more than 5x "
|
||||||
f"the amount of candles {self.name} provides for {timeframe}.")
|
f"the amount of candles {self.name} provides for {timeframe}.")
|
||||||
elif required_candle_call_count > 1:
|
elif required_candle_call_count > 1:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f"This strategy requires {startup_candles} candles to start, which is more than "
|
f"This strategy requires {startup_candles} candles to start, which is more than "
|
||||||
f"the amount of candles {self.name} provides for {timeframe}.")
|
f"the amount of candles {self.name} provides for {timeframe}.")
|
||||||
if required_candle_call_count > 1:
|
if required_candle_call_count > 1:
|
||||||
@@ -3098,3 +3099,4 @@ class Exchange:
|
|||||||
# describes the min amt for a tier, and the lowest tier will always go down to 0
|
# describes the min amt for a tier, and the lowest tier will always go down to 0
|
||||||
else:
|
else:
|
||||||
raise ExchangeError(f"Cannot get maintenance ratio using {self.name}")
|
raise ExchangeError(f"Cannot get maintenance ratio using {self.name}")
|
||||||
|
raise ExchangeError(f"Cannot get maintenance ratio using {self.name}")
|
||||||
|
|||||||
+6
-3
@@ -7,8 +7,6 @@ import logging
|
|||||||
import sys
|
import sys
|
||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
from freqtrade.util.gc_setup import gc_set_threshold
|
|
||||||
|
|
||||||
|
|
||||||
# check min. python version
|
# check min. python version
|
||||||
if sys.version_info < (3, 9): # pragma: no cover
|
if sys.version_info < (3, 9): # pragma: no cover
|
||||||
@@ -16,8 +14,10 @@ if sys.version_info < (3, 9): # pragma: no cover
|
|||||||
|
|
||||||
from freqtrade import __version__
|
from freqtrade import __version__
|
||||||
from freqtrade.commands import Arguments
|
from freqtrade.commands import Arguments
|
||||||
from freqtrade.exceptions import FreqtradeException, OperationalException
|
from freqtrade.constants import DOCS_LINK
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('freqtrade')
|
logger = logging.getLogger('freqtrade')
|
||||||
@@ -56,6 +56,9 @@ def main(sysargv: Optional[List[str]] = None) -> None:
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logger.info('SIGINT received, aborting ...')
|
logger.info('SIGINT received, aborting ...')
|
||||||
return_code = 0
|
return_code = 0
|
||||||
|
except ConfigurationError as e:
|
||||||
|
logger.error(f"Configuration error: {e}\n"
|
||||||
|
f"Please make sure to review the documentation at {DOCS_LINK}.")
|
||||||
except FreqtradeException as e:
|
except FreqtradeException as e:
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
return_code = 2
|
return_code = 2
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from freqtrade.data.btanalysis import (delete_backtest_result, get_backtest_resu
|
|||||||
get_backtest_resultlist, load_and_merge_backtest_result,
|
get_backtest_resultlist, load_and_merge_backtest_result,
|
||||||
update_backtest_metadata)
|
update_backtest_metadata)
|
||||||
from freqtrade.enums import BacktestState
|
from freqtrade.enums import BacktestState
|
||||||
from freqtrade.exceptions import DependencyException, OperationalException
|
from freqtrade.exceptions import ConfigurationError, DependencyException, OperationalException
|
||||||
from freqtrade.exchange.common import remove_exchange_credentials
|
from freqtrade.exchange.common import remove_exchange_credentials
|
||||||
from freqtrade.misc import deep_merge_dicts, is_file_in_dir
|
from freqtrade.misc import deep_merge_dicts, is_file_in_dir
|
||||||
from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestMetadataUpdate,
|
from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestMetadataUpdate,
|
||||||
@@ -98,10 +98,12 @@ def __run_backtest_bg(btconfig: Config):
|
|||||||
|
|
||||||
logger.info("Backtest finished.")
|
logger.info("Backtest finished.")
|
||||||
|
|
||||||
|
except ConfigurationError as e:
|
||||||
|
logger.error(f"Backtesting encountered a configuration Error: {e}")
|
||||||
|
|
||||||
except (Exception, OperationalException, DependencyException) as e:
|
except (Exception, OperationalException, DependencyException) as e:
|
||||||
logger.exception(f"Backtesting caused an error: {e}")
|
logger.exception(f"Backtesting caused an error: {e}")
|
||||||
ApiBG.bt['bt_error'] = str(e)
|
ApiBG.bt['bt_error'] = str(e)
|
||||||
pass
|
|
||||||
finally:
|
finally:
|
||||||
ApiBG.bgtask_running = False
|
ApiBG.bgtask_running = False
|
||||||
|
|
||||||
|
|||||||
@@ -51,15 +51,16 @@ def test_start_trading_fail(mocker, caplog):
|
|||||||
'trade',
|
'trade',
|
||||||
'-c', 'tests/testdata/testconfigs/main_test_config.json'
|
'-c', 'tests/testdata/testconfigs/main_test_config.json'
|
||||||
]
|
]
|
||||||
start_trading(get_args(args))
|
with pytest.raises(OperationalException):
|
||||||
|
start_trading(get_args(args))
|
||||||
assert exitmock.call_count == 1
|
assert exitmock.call_count == 1
|
||||||
|
|
||||||
exitmock.reset_mock()
|
exitmock.reset_mock()
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
mocker.patch("freqtrade.worker.Worker.__init__", MagicMock(side_effect=OperationalException))
|
mocker.patch("freqtrade.worker.Worker.__init__", MagicMock(side_effect=OperationalException))
|
||||||
start_trading(get_args(args))
|
with pytest.raises(OperationalException):
|
||||||
|
start_trading(get_args(args))
|
||||||
assert exitmock.call_count == 0
|
assert exitmock.call_count == 0
|
||||||
assert log_has('Fatal exception!', caplog)
|
|
||||||
|
|
||||||
|
|
||||||
def test_start_webserver(mocker, caplog):
|
def test_start_webserver(mocker, caplog):
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ from numpy import NaN
|
|||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.enums import CandleType, MarginMode, RunMode, TradingMode
|
from freqtrade.enums import CandleType, MarginMode, RunMode, TradingMode
|
||||||
from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError,
|
from freqtrade.exceptions import (ConfigurationError, DDosProtection, DependencyException,
|
||||||
InsufficientFundsError, InvalidOrderException,
|
ExchangeError, InsufficientFundsError, InvalidOrderException,
|
||||||
OperationalException, PricingError, TemporaryError)
|
OperationalException, PricingError, TemporaryError)
|
||||||
from freqtrade.exchange import (Binance, Bybit, Exchange, Kraken, market_is_active,
|
from freqtrade.exchange import (Binance, Bybit, Exchange, Kraken, market_is_active,
|
||||||
timeframe_to_prev_date)
|
timeframe_to_prev_date)
|
||||||
@@ -595,7 +595,7 @@ def test_validate_stakecurrency_error(default_conf, mocker, caplog):
|
|||||||
mocker.patch(f'{EXMS}.validate_pairs')
|
mocker.patch(f'{EXMS}.validate_pairs')
|
||||||
mocker.patch(f'{EXMS}.validate_timeframes')
|
mocker.patch(f'{EXMS}.validate_timeframes')
|
||||||
mocker.patch(f'{EXMS}._load_async_markets')
|
mocker.patch(f'{EXMS}._load_async_markets')
|
||||||
with pytest.raises(OperationalException,
|
with pytest.raises(ConfigurationError,
|
||||||
match=r'XRP is not available as stake on .*'
|
match=r'XRP is not available as stake on .*'
|
||||||
'Available currencies are: BTC, ETH, USDT'):
|
'Available currencies are: BTC, ETH, USDT'):
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
@@ -800,12 +800,12 @@ def test_validate_timeframes_failed(default_conf, mocker):
|
|||||||
mocker.patch(f'{EXMS}.validate_pairs')
|
mocker.patch(f'{EXMS}.validate_pairs')
|
||||||
mocker.patch(f'{EXMS}.validate_stakecurrency')
|
mocker.patch(f'{EXMS}.validate_stakecurrency')
|
||||||
mocker.patch(f'{EXMS}.validate_pricing')
|
mocker.patch(f'{EXMS}.validate_pricing')
|
||||||
with pytest.raises(OperationalException,
|
with pytest.raises(ConfigurationError,
|
||||||
match=r"Invalid timeframe '3m'. This exchange supports.*"):
|
match=r"Invalid timeframe '3m'. This exchange supports.*"):
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
default_conf["timeframe"] = "15s"
|
default_conf["timeframe"] = "15s"
|
||||||
|
|
||||||
with pytest.raises(OperationalException,
|
with pytest.raises(ConfigurationError,
|
||||||
match=r"Timeframes < 1m are currently not supported by Freqtrade."):
|
match=r"Timeframes < 1m are currently not supported by Freqtrade."):
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|||||||
+17
-1
@@ -8,7 +8,7 @@ import pytest
|
|||||||
|
|
||||||
from freqtrade.commands import Arguments
|
from freqtrade.commands import Arguments
|
||||||
from freqtrade.enums import State
|
from freqtrade.enums import State
|
||||||
from freqtrade.exceptions import FreqtradeException, OperationalException
|
from freqtrade.exceptions import ConfigurationError, FreqtradeException, OperationalException
|
||||||
from freqtrade.freqtradebot import FreqtradeBot
|
from freqtrade.freqtradebot import FreqtradeBot
|
||||||
from freqtrade.main import main
|
from freqtrade.main import main
|
||||||
from freqtrade.worker import Worker
|
from freqtrade.worker import Worker
|
||||||
@@ -141,6 +141,22 @@ def test_main_operational_exception1(mocker, default_conf, caplog) -> None:
|
|||||||
assert log_has_re(r'SIGINT.*', caplog)
|
assert log_has_re(r'SIGINT.*', caplog)
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_ConfigurationError(mocker, default_conf, caplog) -> None:
|
||||||
|
patch_exchange(mocker)
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.commands.list_commands.list_available_exchanges',
|
||||||
|
MagicMock(side_effect=ConfigurationError('Oh snap!'))
|
||||||
|
)
|
||||||
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
|
args = ['list-exchanges']
|
||||||
|
|
||||||
|
# Test Main + the KeyboardInterrupt exception
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
main(args)
|
||||||
|
assert log_has_re('Configuration error: Oh snap!', caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_main_reload_config(mocker, default_conf, caplog) -> None:
|
def test_main_reload_config(mocker, default_conf, caplog) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock())
|
||||||
|
|||||||
Reference in New Issue
Block a user