From ae7e95cba6ac6959dab4db88a63c52e2ac4f9c9a Mon Sep 17 00:00:00 2001 From: TPTBusiness Date: Sun, 5 Apr 2026 12:58:41 +0200 Subject: [PATCH] fix: Handle failed experiments in feedback step to prevent crashes Problem: - When Qlib Docker backtest failed (result is None), the experiment was marked as failed - However, the 'feedback' step still tried to call self.factor_summarizer.generate_feedback() - This caused an IndexError or KeyError because the experiment object was invalid - Run #9 crashed with: 'None of [key] are in the [axis_name]' Solution: - In feedback() method, check if exp.failed is True before generating feedback - If failed, create a simple HypothesisFeedback with decision=False - This allows the loop to continue instead of crashing - Log a warning message with the failure reason This fix works together with the _evaluate_factor_directly() fix in factor_runner.py to ensure that even when Docker fails, the loop continues smoothly. --- rdagent/app/qlib_rd_loop/quant.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/rdagent/app/qlib_rd_loop/quant.py b/rdagent/app/qlib_rd_loop/quant.py index 65918dbe..23b528e8 100644 --- a/rdagent/app/qlib_rd_loop/quant.py +++ b/rdagent/app/qlib_rd_loop/quant.py @@ -217,10 +217,27 @@ class QuantRDLoop(RDLoop): decision=False, ) else: - if prev_out["direct_exp_gen"]["propose"].action == "factor": - feedback = self.factor_summarizer.generate_feedback(prev_out["running"], self.trace) - elif prev_out["direct_exp_gen"]["propose"].action == "model": - feedback = self.model_summarizer.generate_feedback(prev_out["running"], self.trace) + # Handle cases where the experiment failed during execution (e.g., Docker error) + exp = prev_out.get("running") + if exp is not None and getattr(exp, "failed", False): + reason = getattr(exp, "failure_reason", "Unknown failure reason") + factor_name = "unknown" + if hasattr(exp, "hypothesis") and exp.hypothesis is not None: + factor_name = getattr(exp.hypothesis, "hypothesis", "unknown") + + logger.warning(f"Skipping feedback for failed factor '{factor_name}'. Reason: {reason}") + feedback = HypothesisFeedback( + observations=f"Factor '{factor_name}' failed execution.", + hypothesis_evaluation="Failed", + new_hypothesis="Try a different approach.", + reason=reason, + decision=False, + ) + else: + if prev_out["direct_exp_gen"]["propose"].action == "factor": + feedback = self.factor_summarizer.generate_feedback(prev_out["running"], self.trace) + elif prev_out["direct_exp_gen"]["propose"].action == "model": + feedback = self.model_summarizer.generate_feedback(prev_out["running"], self.trace) # NOTE: DB save is handled by factor_runner.py _save_result_to_database() # which runs immediately after Docker execution. No duplicate save needed here.