fix: Graceful shutdown in case of failed initialization

This commit is contained in:
Matthias
2026-04-04 08:51:04 +02:00
parent 1ebd75b32a
commit e3c316f311
+97 -92
View File
@@ -92,100 +92,105 @@ class FreqtradeBot(LoggingMixin):
exchange_config: ExchangeConfig = deepcopy(config["exchange"])
# Remove credentials from original exchange config to avoid accidental credential exposure
remove_exchange_credentials(config["exchange"], True)
self.exchange = ExchangeResolver.load_exchange(
self.config, exchange_config=exchange_config, load_leverage_tiers=True
)
self.strategy: IStrategy = StrategyResolver.load_strategy(self.config)
# Check config consistency here since strategies can set certain options
validate_config_consistency(config)
# Re-validate exchange compatibility
self.exchange.validate_config(self.config)
init_db(self.config["db_url"])
self.wallets = Wallets(self.config, self.exchange)
PairLocks.timeframe = self.config["timeframe"]
self.trading_mode: TradingMode = self.config.get("trading_mode", TradingMode.SPOT)
self.margin_mode: MarginMode = self.config.get("margin_mode", MarginMode.NONE)
self.last_process: datetime | None = None
# RPC runs in separate threads, can start handling external commands just after
# initialization, even before Freqtradebot has a chance to start its throttling,
# so anything in the Freqtradebot instance should be ready (initialized), including
# the initial state of the bot.
# Keep this at the end of this initialization method.
self.rpc: RPCManager = RPCManager(self)
self.dataprovider = DataProvider(self.config, self.exchange, rpc=self.rpc)
self.pairlists = PairListManager(self.exchange, self.config, self.dataprovider)
self.dataprovider.add_pairlisthandler(self.pairlists)
# Attach Dataprovider to strategy instance
self.strategy.dp = self.dataprovider
# Attach Wallets to strategy instance
self.strategy.wallets = self.wallets
# Init ExternalMessageConsumer if enabled
self.emc: ExternalMessageConsumer | None = (
ExternalMessageConsumer(self.config, self.dataprovider)
if self.config.get("external_message_consumer", {}).get("enabled", False)
else None
)
logger.info("Starting initial pairlist refresh")
with MeasureTime(
lambda duration, _: logger.info(f"Initial Pairlist refresh took {duration:.2f}s"), 0
):
self.active_pair_whitelist = self._refresh_active_whitelist()
# Set initial bot state from config
initial_state = self.config.get("initial_state")
self.state = State[initial_state.upper()] if initial_state else State.STOPPED
# Protect exit-logic from forcesell and vice versa
self._exit_lock = Lock()
timeframe_secs = timeframe_to_seconds(self.strategy.timeframe)
self._exit_reason_cache = PeriodicCache(100, ttl=timeframe_secs)
LoggingMixin.__init__(self, logger, timeframe_secs)
self._schedule = Scheduler()
if self.trading_mode == TradingMode.FUTURES:
def update():
self.update_funding_fees()
self.update_all_liquidation_prices()
self.wallets.update()
# This would be more efficient if scheduled in utc time, and performed at each
# funding interval, specified by funding_fee_times on the exchange classes
# However, this reduces the precision - and might therefore lead to problems.
for time_slot in range(0, 24):
for minutes in [1, 31]:
t = str(time(time_slot, minutes, 2))
self._schedule.every().day.at(t).do(update)
self._schedule.every().day.at("00:02").do(self.exchange.ws_connection_reset)
self.strategy.ft_bot_start()
# Initialize protections AFTER bot start - otherwise parameters are not loaded.
self.protections = ProtectionManager(self.config, self.strategy.protections)
def log_took_too_long(duration: float, time_limit: float):
logger.warning(
f"Strategy analysis took {duration:.2f}s, more than 25% of the timeframe "
f"({time_limit:.2f}s). This can lead to delayed orders and missed signals."
"Consider either reducing the amount of work your strategy performs "
"or reduce the amount of pairs in the Pairlist."
try:
self.exchange = ExchangeResolver.load_exchange(
self.config, exchange_config=exchange_config, load_leverage_tiers=True
)
self._measure_execution = MeasureTime(log_took_too_long, timeframe_secs * 0.25)
self.strategy: IStrategy = StrategyResolver.load_strategy(self.config)
# Check config consistency here since strategies can set certain options
validate_config_consistency(config)
# Re-validate exchange compatibility
self.exchange.validate_config(self.config)
init_db(self.config["db_url"])
self.wallets = Wallets(self.config, self.exchange)
PairLocks.timeframe = self.config["timeframe"]
self.trading_mode: TradingMode = self.config.get("trading_mode", TradingMode.SPOT)
self.margin_mode: MarginMode = self.config.get("margin_mode", MarginMode.NONE)
self.last_process: datetime | None = None
# RPC runs in separate threads, can start handling external commands just after
# initialization, even before Freqtradebot has a chance to start its throttling,
# so anything in the Freqtradebot instance should be ready (initialized), including
# the initial state of the bot.
# Keep this at the end of this initialization method.
self.rpc: RPCManager = RPCManager(self)
self.dataprovider = DataProvider(self.config, self.exchange, rpc=self.rpc)
self.pairlists = PairListManager(self.exchange, self.config, self.dataprovider)
self.dataprovider.add_pairlisthandler(self.pairlists)
# Attach Dataprovider to strategy instance
self.strategy.dp = self.dataprovider
# Attach Wallets to strategy instance
self.strategy.wallets = self.wallets
# Init ExternalMessageConsumer if enabled
self.emc: ExternalMessageConsumer | None = (
ExternalMessageConsumer(self.config, self.dataprovider)
if self.config.get("external_message_consumer", {}).get("enabled", False)
else None
)
logger.info("Starting initial pairlist refresh")
with MeasureTime(
lambda duration, _: logger.info(f"Initial Pairlist refresh took {duration:.2f}s"), 0
):
self.active_pair_whitelist = self._refresh_active_whitelist()
# Set initial bot state from config
initial_state = self.config.get("initial_state")
self.state = State[initial_state.upper()] if initial_state else State.STOPPED
# Protect exit-logic from forcesell and vice versa
self._exit_lock = Lock()
timeframe_secs = timeframe_to_seconds(self.strategy.timeframe)
self._exit_reason_cache = PeriodicCache(100, ttl=timeframe_secs)
LoggingMixin.__init__(self, logger, timeframe_secs)
self._schedule = Scheduler()
if self.trading_mode == TradingMode.FUTURES:
def update():
self.update_funding_fees()
self.update_all_liquidation_prices()
self.wallets.update()
# This would be more efficient if scheduled in utc time, and performed at each
# funding interval, specified by funding_fee_times on the exchange classes
# However, this reduces the precision - and might therefore lead to problems.
for time_slot in range(0, 24):
for minutes in [1, 31]:
t = str(time(time_slot, minutes, 2))
self._schedule.every().day.at(t).do(update)
self._schedule.every().day.at("00:02").do(self.exchange.ws_connection_reset)
self.strategy.ft_bot_start()
# Initialize protections AFTER bot start - otherwise parameters are not loaded.
self.protections = ProtectionManager(self.config, self.strategy.protections)
def log_took_too_long(duration: float, time_limit: float):
logger.warning(
f"Strategy analysis took {duration:.2f}s, more than 25% of the timeframe "
f"({time_limit:.2f}s). This can lead to delayed orders and missed signals."
"Consider either reducing the amount of work your strategy performs "
"or reduce the amount of pairs in the Pairlist."
)
self._measure_execution = MeasureTime(log_took_too_long, timeframe_secs * 0.25)
except Exception as e:
# Graceful shutdown in case of failed initialization.
self.cleanup()
raise e from e
def notify_status(self, msg: str, msg_type=RPCMessageType.STATUS) -> None:
"""