mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-08-08 12:37:44 +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:
@@ -14,7 +14,7 @@ from rdagent.components.workflow.conf import BasePropSetting
|
||||
from rdagent.components.workflow.rd_loop import RDLoop
|
||||
from rdagent.core.conf import RD_AGENT_SETTINGS
|
||||
from rdagent.core.developer import Developer
|
||||
from rdagent.core.exception import FactorEmptyError, ModelEmptyError
|
||||
from rdagent.core.exception import FactorEmptyError, LLMUnavailableError, ModelEmptyError
|
||||
from rdagent.core.proposal import (
|
||||
Experiment2Feedback,
|
||||
ExperimentPlan,
|
||||
@@ -33,6 +33,7 @@ class QuantRDLoop(RDLoop):
|
||||
skip_loop_error = (
|
||||
FactorEmptyError,
|
||||
ModelEmptyError,
|
||||
LLMUnavailableError, # LLM timeout after all retries → skip loop, don't crash
|
||||
)
|
||||
|
||||
def __init__(self, PROP_SETTING: BasePropSetting):
|
||||
@@ -94,10 +95,15 @@ class QuantRDLoop(RDLoop):
|
||||
def coding(self, prev_out: dict[str, Any]):
|
||||
exp = None
|
||||
try:
|
||||
if prev_out["direct_exp_gen"]["propose"].action == "factor":
|
||||
exp = self.factor_coder.develop(prev_out["direct_exp_gen"]["exp_gen"])
|
||||
elif prev_out["direct_exp_gen"]["propose"].action == "model":
|
||||
exp = self.model_coder.develop(prev_out["direct_exp_gen"]["exp_gen"])
|
||||
direct = prev_out.get("direct_exp_gen")
|
||||
if not direct:
|
||||
# Loop was reset (LoopResumeError) while this step was already queued.
|
||||
# Treat as empty so skip_loop_error skips this iteration cleanly.
|
||||
raise FactorEmptyError("direct_exp_gen result missing after loop reset")
|
||||
if direct["propose"].action == "factor":
|
||||
exp = self.factor_coder.develop(direct["exp_gen"])
|
||||
elif direct["propose"].action == "model":
|
||||
exp = self.model_coder.develop(direct["exp_gen"])
|
||||
logger.log_object(exp, tag="coder result")
|
||||
except (FactorEmptyError, ModelEmptyError) as e:
|
||||
logger.warning(f"Coding failed with {type(e).__name__}: {e}")
|
||||
|
||||
Reference in New Issue
Block a user