mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-08-07 20:17:45 +00:00
feat: unified backtest engine, LLM error handling, strategy refactor
- Add vbt_backtest.py as single source of truth for all metric formulas (Sharpe, drawdown, IC, transaction costs) — backtest_engine.py and strategy_orchestrator.py now delegate to it - Add LLMUnavailableError to exception.py; rd_loop.py catches it at the proposal stage and raises LoopResumeError to avoid corrupting trace history with None hypotheses - Guard record() against None exp/hypothesis so loop resets leave trace.hist in a consistent state - Refactor strategy_orchestrator and optuna_optimizer to use unified backtest path; remove duplicate metric calculation code - Add predix_rebacktest_unified.py script for offline re-evaluation - Update tests and README Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -182,7 +182,15 @@ class RDLoop(LoopBase, metaclass=LoopMeta):
|
||||
return modified_feedback
|
||||
|
||||
def _propose(self):
|
||||
hypothesis = self.hypothesis_gen.gen(self.trace, self.plan)
|
||||
from rdagent.core.exception import LLMUnavailableError
|
||||
|
||||
try:
|
||||
hypothesis = self.hypothesis_gen.gen(self.trace, self.plan)
|
||||
except LLMUnavailableError as e:
|
||||
# LLM timeout at the proposal stage: skip_loop_error would leave
|
||||
# hypothesis=None in trace.hist and corrupt all future iterations.
|
||||
# Reset the whole loop instead so state stays consistent.
|
||||
raise self.LoopResumeError("LLM unavailable during proposal, resetting loop") from e
|
||||
|
||||
# user can change the hypothesis here
|
||||
hypothesis = self._interact_hypo(hypothesis)
|
||||
@@ -237,5 +245,10 @@ class RDLoop(LoopBase, metaclass=LoopMeta):
|
||||
|
||||
def record(self, prev_out: dict[str, Any]):
|
||||
feedback = prev_out["feedback"]
|
||||
exp = prev_out.get("running") or prev_out.get("coding") or prev_out.get("direct_exp_gen", {}).get("exp_gen")
|
||||
exp = prev_out.get("running") or prev_out.get("coding") or (prev_out.get("direct_exp_gen") or {}).get("exp_gen")
|
||||
if exp is None or getattr(exp, "hypothesis", None) is None:
|
||||
# Loop was reset or skipped — nothing valid to record in trace history.
|
||||
# Storing None here would corrupt quant_proposal.py which reads
|
||||
# trace.hist[-1][0].hypothesis on the next iteration.
|
||||
return
|
||||
self.trace.sync_dag_parent_and_hist((exp, feedback), prev_out[self.LOOP_IDX_KEY])
|
||||
|
||||
Reference in New Issue
Block a user