Merge pull request #9975 from freqtrade/feat/configError

exception ConfigurationError
This commit is contained in:
Matthias
2024-03-21 08:38:45 +01:00
committed by GitHub
20 changed files with 113 additions and 86 deletions

View File

@@ -129,6 +129,8 @@ Below is an outline of exception inheritance hierarchy:
+ FreqtradeException
|
+---+ OperationalException
| |
| +---+ ConfigurationError
|
+---+ DependencyException
| |

View File

@@ -4,7 +4,7 @@ from typing import Any, Dict
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.exceptions import ConfigurationError, OperationalException
logger = logging.getLogger(__name__)
@@ -34,9 +34,9 @@ def setup_analyze_configuration(args: Dict[str, Any], method: RunMode) -> Dict[s
btfile = Path(config['exportfilename'])
signals_file = f"{btfile.parent}/{btfile.stem}_signals.pkl"
else:
raise OperationalException(f"{config['exportfilename']} does not exist.")
raise ConfigurationError(f"{config['exportfilename']} does not exist.")
else:
raise OperationalException('exportfilename not in config.')
raise ConfigurationError('exportfilename not in config.')
if (not Path(signals_file).exists()):
raise OperationalException(

View File

@@ -9,7 +9,7 @@ from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_forma
convert_trades_to_ohlcv)
from freqtrade.data.history import download_data_main
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.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
from freqtrade.resolvers import ExchangeResolver
@@ -21,11 +21,11 @@ logger = logging.getLogger(__name__)
def _check_data_config_download_sanity(config: Config) -> None:
if 'days' in config and 'timerange' in config:
raise OperationalException("--days and --timerange are mutually exclusive. "
"You can only specify one or the other.")
raise ConfigurationError("--days and --timerange are mutually exclusive. "
"You can only specify one or the other.")
if 'pairs' not in config:
raise OperationalException(
raise ConfigurationError(
"Downloading data requires a list of pairs. "
"Please check the documentation on how to configure this.")

View File

@@ -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.constants import USERPATH_STRATEGIES
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
@@ -89,7 +89,7 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
deploy_new_strategy(args['strategy'], new_path, args['template'])
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):

View File

@@ -10,7 +10,7 @@ from tabulate import tabulate
from freqtrade.configuration import setup_utils_configuration
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.misc import parse_db_uri_for_logging, plural
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)
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"])}"')
init_db(config['db_url'])

View File

@@ -4,7 +4,7 @@ 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.exceptions import ConfigurationError, OperationalException
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):
wallet = fmt_coin(wallet_size, 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"Wallet is calculated as `dry_run_wallet * tradable_balance_ratio`."
)

View File

@@ -2,12 +2,12 @@ from typing import Any, Dict
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.exceptions import ConfigurationError
def validate_plot_args(args: Dict[str, Any]) -> None:
if not args.get('datadir') and not args.get('config'):
raise OperationalException(
raise ConfigurationError(
"You need to specify either `--datadir` or `--config` "
"for plot-profit and plot-dataframe.")

View File

@@ -23,11 +23,6 @@ def start_trading(args: Dict[str, Any]) -> int:
signal.signal(signal.SIGTERM, term_handler)
worker = Worker(args)
worker.run()
except Exception as e:
logger.error(str(e))
logger.exception("Fatal exception!")
except (KeyboardInterrupt):
logger.info('SIGINT received, aborting ...')
finally:
if worker:
logger.info("worker found ... calling exit")

View File

@@ -9,7 +9,7 @@ from jsonschema.exceptions import ValidationError, best_match
from freqtrade import constants
from freqtrade.configuration.deprecated_settings import process_deprecated_setting
from freqtrade.enums import RunMode, TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.exceptions import ConfigurationError
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,
since strategies can set certain configuration settings too.
: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
@@ -98,12 +98,12 @@ def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = Fal
def _validate_unlimited_amount(conf: Dict[str, Any]) -> None:
"""
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')
and conf.get('max_open_trades') == float('inf')
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:
@@ -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
if (conf.get('order_types', {}).get('entry') == 'market'
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".')
if (conf.get('order_types', {}).get('exit') == 'market'
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:
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.'
)
# 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_positive == 0.0:
raise OperationalException(
raise ConfigurationError(
'The config trailing_only_offset_is_reached needs '
'trailing_stop_positive_offset to be more than 0 in your config.')
if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive:
raise OperationalException(
raise ConfigurationError(
'The config trailing_stop_positive_offset needs '
'to be greater than trailing_stop_positive in your config.')
# Fetch again without default
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 '
'to avoid problems with sell orders.'
)
@@ -162,7 +162,7 @@ def _validate_edge(conf: Dict[str, Any]) -> None:
return
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."
)
@@ -178,7 +178,7 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None:
for pl in conf.get('pairlists', [{'method': 'StaticPairList'}]):
if (isinstance(pl, dict) and pl.get('method') == 'StaticPairList'
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:
@@ -188,13 +188,13 @@ def _validate_protections(conf: Dict[str, Any]) -> None:
for prot in conf.get('protections', []):
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"
f"Please fix the protection {prot.get('method')}"
)
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"
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')
if ob_min is not None and ob_max is not None and ask_strategy.get('use_order_book'):
if ob_min != ob_max:
raise OperationalException(
raise ConfigurationError(
"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."
)
@@ -234,7 +234,7 @@ def _validate_time_in_force(conf: Dict[str, Any]) -> None:
time_in_force = conf.get('order_time_in_force', {})
if 'buy' in time_in_force or 'sell' in time_in_force:
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'.")
else:
logger.warning(
@@ -255,7 +255,7 @@ def _validate_order_types(conf: Dict[str, Any]) -> None:
'forcesell', 'emergencyexit', 'forceexit', 'forceentry']
if any(x in order_types for x in old_order_types):
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
raise OperationalException(
raise ConfigurationError(
"Please migrate your order_types settings to use the new wording.")
else:
logger.warning(
@@ -280,7 +280,7 @@ def _validate_unfilledtimeout(conf: Dict[str, Any]) -> None:
unfilledtimeout = conf.get('unfilledtimeout', {})
if any(x in unfilledtimeout for x in ['buy', 'sell']):
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
raise OperationalException(
raise ConfigurationError(
"Please migrate your unfilledtimeout settings to use the new wording.")
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('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
raise OperationalException(
raise ConfigurationError(
"Please migrate your pricing settings to use the new wording.")
else:
@@ -331,7 +331,7 @@ def _validate_freqai_hyperopt(conf: Dict[str, Any]) -> None:
freqai_enabled = conf.get('freqai', {}).get('enabled', False)
analyze_per_epoch = conf.get('analyze_per_epoch', False)
if analyze_per_epoch and freqai_enabled:
raise OperationalException(
raise ConfigurationError(
'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:
offending_lines.append(tf)
if offending_lines:
raise OperationalException(
raise ConfigurationError(
f"Main timeframe of {main_tf} must be smaller or equal to FreqAI "
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')
freqai_backtest_live_models = conf.get('freqai_backtest_live_models', False)
if freqai_backtest_live_models and freqai_enabled and timerange:
raise OperationalException(
raise ConfigurationError(
'Using timerange parameter is not supported with '
'--freqai-backtest-live-models parameter.')
if freqai_backtest_live_models and not freqai_enabled:
raise OperationalException(
raise ConfigurationError(
'Using --freqai-backtest-live-models parameter is only '
'supported with a FreqAI strategy.')
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.')
@@ -386,12 +386,12 @@ def _validate_consumers(conf: Dict[str, Any]) -> None:
emc_conf = conf.get('external_message_consumer', {})
if emc_conf.get('enabled', False):
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', [])]
duplicates = [item for item, count in Counter(producer_names).items() if count > 1]
if duplicates:
raise OperationalException(
raise ConfigurationError(
f"Producer names must be unique. Duplicate: {', '.join(duplicates)}")
if conf.get('process_only_new_candles', True):
# Warning here or require it?

View File

@@ -6,7 +6,7 @@ import logging
from typing import Optional
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException
from freqtrade.exceptions import ConfigurationError, OperationalException
logger = logging.getLogger(__name__)
@@ -41,7 +41,7 @@ def process_removed_setting(config: Config,
section1_config = config.get(section1, {})
if name1 in section1_config:
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"Please delete it from your configuration and use the `{section_2}` "
"setting instead."
@@ -122,7 +122,7 @@ def process_temporary_deprecated_settings(config: Config) -> None:
None, 'ignore_roi_if_entry_signal')
if (config.get('edge', {}).get('enabled', False)
and 'capital_available_percentage' in config.get('edge', {})):
raise OperationalException(
raise ConfigurationError(
"DEPRECATED: "
"Using 'edge.capital_available_percentage' has been deprecated in favor of "
"'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:
raise OperationalException(
raise ConfigurationError(
"DEPRECATED: 'ticker_interval' detected. "
"Please use 'timeframe' instead of 'ticker_interval."
)

View File

@@ -11,7 +11,7 @@ from typing import Any, Dict, List, Optional
import rapidjson
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
@@ -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
except rapidjson.JSONDecodeError as e:
err_range = log_config_error_range(path, str(e))
raise OperationalException(
raise ConfigurationError(
f'{e}\n'
f'Please verify the following segment of your configuration:\n{err_range}'
if err_range else 'Please verify your configuration file for syntax errors.'
@@ -83,7 +83,7 @@ def load_from_files(
"""
config: Config = {}
if level > 5:
raise OperationalException("Config loop detected.")
raise ConfigurationError("Config loop detected.")
if not files:
return deepcopy(MINIMAL_CONFIG)

View File

@@ -9,7 +9,7 @@ from typing import Optional
from typing_extensions import Self
from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.exceptions import OperationalException
from freqtrade.exceptions import ConfigurationError
logger = logging.getLogger(__name__)
@@ -156,7 +156,7 @@ class TimeRange:
else:
stop = int(stops)
if start > stop > 0:
raise OperationalException(
raise ConfigurationError(
f'Start date is after stop date for timerange "{text}"')
return cls(stype[0], stype[1], start, stop)
raise OperationalException(f'Incorrect syntax for timerange "{text}"')
raise ConfigurationError(f'Incorrect syntax for timerange "{text}"')

View File

@@ -11,7 +11,7 @@ import numpy as np
import pandas as pd
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.optimize.backtest_caching import get_backtest_metadata_filename
from freqtrade.persistence import LocalTrade, Trade, init_db
@@ -106,7 +106,7 @@ def get_latest_hyperopt_file(
directory = Path(directory)
if predef_filename:
if Path(predef_filename).is_absolute():
raise OperationalException(
raise ConfigurationError(
"--hyperopt-filename expects only the filename, not an absolute path.")
return directory / predef_filename
return directory / get_latest_hyperopt_filename(directory)

View File

@@ -12,6 +12,12 @@ class OperationalException(FreqtradeException):
"""
class ConfigurationError(OperationalException):
"""
Configuration error. Usually caused by invalid configuration.
"""
class DependencyException(FreqtradeException):
"""
Indicates that an assumed dependency is not met.

View File

@@ -24,9 +24,10 @@ from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHAN
ListPairsWithTimeframes, MakerTaker, OBLiteral, PairWithTimeframe)
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.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
InvalidOrderException, OperationalException, PricingError,
RetryableOrderError, TemporaryError)
from freqtrade.exceptions import (ConfigurationError, DDosProtection, ExchangeError,
InsufficientFundsError, InvalidOrderException,
OperationalException, PricingError, RetryableOrderError,
TemporaryError)
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, remove_exchange_credentials,
retrier, retrier_async)
from freqtrade.exchange.exchange_utils import (ROUND, ROUND_DOWN, ROUND_UP, CcxtModuleType,
@@ -529,7 +530,7 @@ class Exchange:
)
quote_currencies = self.get_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"Available currencies are: {', '.join(quote_currencies)}")
@@ -597,7 +598,7 @@ class Exchange:
f"is therefore not supported. ccxt fetchOHLCV: {self.exchange_has('fetchOHLCV')}")
if timeframe and (timeframe not in self.timeframes):
raise OperationalException(
raise ConfigurationError(
f"Invalid timeframe '{timeframe}'. This exchange supports: {self.timeframes}")
if (
@@ -605,7 +606,7 @@ class Exchange:
and self._config['runmode'] != RunMode.UTIL_EXCHANGE
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:
"""
@@ -613,7 +614,7 @@ class Exchange:
"""
if any(v == 'market' for k, v in order_types.items()):
if not self.exchange_has('createMarketOrder'):
raise OperationalException(
raise ConfigurationError(
f'Exchange {self.name} does not support market orders.')
self.validate_stop_ordertypes(order_types)
@@ -623,7 +624,7 @@ class Exchange:
"""
if (order_types.get("stoploss_on_exchange")
and not self._ft_has.get("stoploss_on_exchange", False)):
raise OperationalException(
raise ConfigurationError(
f'On exchange stoploss is not supported for {self.name}.'
)
if self.trading_mode == TradingMode.FUTURES:
@@ -633,17 +634,17 @@ class Exchange:
and 'stoploss_price_type' in order_types
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}.'
)
def validate_pricing(self, pricing: Dict) -> None:
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 (
not self.exchange_has('fetchTicker')
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:
"""
@@ -651,7 +652,7 @@ class Exchange:
"""
if any(v.upper() not in self._ft_has["order_time_in_force"]
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.')
def validate_required_startup_candles(self, startup_candles: int, timeframe: str) -> int:
@@ -673,12 +674,12 @@ class Exchange:
if required_candle_call_count > 5:
# Only allow 5 calls per pair to somewhat limit the impact
raise OperationalException(
raise ConfigurationError(
f"This strategy requires {startup_candles} candles to start, "
"which is more than 5x "
f"the amount of candles {self.name} provides for {timeframe}.")
elif required_candle_call_count > 1:
raise OperationalException(
raise ConfigurationError(
f"This strategy requires {startup_candles} candles to start, which is more than "
f"the amount of candles {self.name} provides for {timeframe}.")
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
else:
raise ExchangeError(f"Cannot get maintenance ratio using {self.name}")
raise ExchangeError(f"Cannot get maintenance ratio using {self.name}")

View File

@@ -7,8 +7,6 @@ import logging
import sys
from typing import Any, List, Optional
from freqtrade.util.gc_setup import gc_set_threshold
# check min. python version
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.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.util.gc_setup import gc_set_threshold
logger = logging.getLogger('freqtrade')
@@ -56,6 +56,9 @@ def main(sysargv: Optional[List[str]] = None) -> None:
except KeyboardInterrupt:
logger.info('SIGINT received, aborting ...')
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:
logger.error(str(e))
return_code = 2

View File

@@ -14,7 +14,7 @@ from freqtrade.data.btanalysis import (delete_backtest_result, get_backtest_resu
get_backtest_resultlist, load_and_merge_backtest_result,
update_backtest_metadata)
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.misc import deep_merge_dicts, is_file_in_dir
from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestMetadataUpdate,
@@ -98,10 +98,12 @@ def __run_backtest_bg(btconfig: Config):
logger.info("Backtest finished.")
except ConfigurationError as e:
logger.error(f"Backtesting encountered a configuration Error: {e}")
except (Exception, OperationalException, DependencyException) as e:
logger.exception(f"Backtesting caused an error: {e}")
ApiBG.bt['bt_error'] = str(e)
pass
finally:
ApiBG.bgtask_running = False

View File

@@ -51,15 +51,16 @@ def test_start_trading_fail(mocker, caplog):
'trade',
'-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
exitmock.reset_mock()
caplog.clear()
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 log_has('Fatal exception!', caplog)
def test_start_webserver(mocker, caplog):

View File

@@ -11,8 +11,8 @@ from numpy import NaN
from pandas import DataFrame
from freqtrade.enums import CandleType, MarginMode, RunMode, TradingMode
from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError,
InsufficientFundsError, InvalidOrderException,
from freqtrade.exceptions import (ConfigurationError, DDosProtection, DependencyException,
ExchangeError, InsufficientFundsError, InvalidOrderException,
OperationalException, PricingError, TemporaryError)
from freqtrade.exchange import (Binance, Bybit, Exchange, Kraken, market_is_active,
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_timeframes')
mocker.patch(f'{EXMS}._load_async_markets')
with pytest.raises(OperationalException,
with pytest.raises(ConfigurationError,
match=r'XRP is not available as stake on .*'
'Available currencies are: BTC, ETH, USDT'):
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_stakecurrency')
mocker.patch(f'{EXMS}.validate_pricing')
with pytest.raises(OperationalException,
with pytest.raises(ConfigurationError,
match=r"Invalid timeframe '3m'. This exchange supports.*"):
Exchange(default_conf)
default_conf["timeframe"] = "15s"
with pytest.raises(OperationalException,
with pytest.raises(ConfigurationError,
match=r"Timeframes < 1m are currently not supported by Freqtrade."):
Exchange(default_conf)

View File

@@ -8,7 +8,7 @@ import pytest
from freqtrade.commands import Arguments
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.main import main
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)
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:
patch_exchange(mocker)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock())