fix: Add get_factor_count() to QuantTrace to prevent parallel run crashes

Problem:
- All 50 parallel runs failed with: AttributeError: 'QuantTrace' object has no attribute 'get_factor_count'
- The _build_strategies_with_ai() method calls self.trace.get_factor_count()
- This method didn't exist in the QuantTrace class

Fix:
- Add get_factor_count() method to QuantTrace class
- Add increment_factor_count() method to track factor generation
- Call increment_factor_count() in running() step when factors are generated
- _build_strategies_with_ai() is already wrapped in try/except for safety

Now the parallel runs will work correctly.
This commit is contained in:
TPTBusiness
2026-04-06 10:52:28 +02:00
parent a8c23bd130
commit ac9bfc6fb4
3 changed files with 87 additions and 7 deletions
+74 -7
View File
@@ -924,6 +924,11 @@ def build_strategies_ai(
"--max-dd",
help="Maximum acceptable drawdown (default: -0.20)",
),
count: int = typer.Option(
1,
"--count", "-c",
help="Number of strategies to generate (default: 1, use 0 for unlimited)",
),
):
"""
Build trading strategies using AI (LLM-based StrategyCoSTEER).
@@ -966,6 +971,31 @@ def build_strategies_ai(
# Load top factors
factors_dir = Path(__file__).parent / "results" / "factors"
# Setup LLM environment (same as quant command)
api_key = os.getenv("OPENROUTER_API_KEY") or os.getenv("OPENAI_API_KEY", "")
api_key_2 = os.getenv("OPENROUTER_API_KEY_2", "")
if api_key and not api_key.startswith("sk-or-"):
# OPENROUTER_API_KEY not set, try to use what we have
api_key = os.getenv("OPENROUTER_API_KEY", api_key)
if "openrouter" in os.getenv("CHAT_MODEL", "").lower() or "openrouter" in os.getenv("OPENAI_API_BASE", "").lower():
# Already configured for OpenRouter
console.print(f"\n[bold blue]🌐 Using OpenRouter: {os.getenv('CHAT_MODEL', 'unknown')}[/bold blue]")
elif api_key:
# Configure OpenRouter
if api_key_2:
os.environ["OPENAI_API_KEY"] = f"{api_key},{api_key_2}"
else:
os.environ["OPENAI_API_KEY"] = api_key
os.environ["OPENAI_API_BASE"] = "https://openrouter.ai/api/v1"
os.environ["CHAT_MODEL"] = os.getenv("OPENROUTER_MODEL", "openrouter/qwen/qwen3.6-plus:free")
console.print(f"\n[bold blue]🌐 Using OpenRouter: {os.environ['CHAT_MODEL']}[/bold blue]")
else:
console.print("[bold red]❌ No API key found. Set OPENROUTER_API_KEY in .env[/bold red]")
return
if not factors_dir.exists():
console.print("[bold red]❌ No factors directory found at results/factors/[/bold red]")
console.print("[yellow]Run 'predix quant' to generate factors first.[/yellow]")
@@ -1014,7 +1044,29 @@ def build_strategies_ai(
max_drawdown=max_drawdown,
)
results = costeer.run(top_factors)
# Generate strategies until we have enough
all_results = []
batch_idx = 0
max_batches = count if count > 0 else 999 # Unlimited if count=0
while len(all_results) < count or count == 0:
if count == 0 and batch_idx >= max_batches:
break # Safety limit for unlimited mode
if count > 0 and batch_idx >= count:
break # Already tried enough times
batch_idx += 1
console.print(f"\n[dim]━━━ Strategy Batch {batch_idx}/{count if count > 0 else ''} ━━━[/dim]")
results = costeer.run(top_factors)
all_results.extend(results)
if count == 0:
console.print(f"\n[dim]Generated {len(all_results)} strategies so far. Press Ctrl+C to stop.[/dim]")
elif len(all_results) < count:
console.print(f"\n[dim]Need {count - len(all_results)} more strategies...[/dim]")
results = all_results[:count] if count > 0 else all_results # Trim to exact count
# Display results
if results:
@@ -1024,20 +1076,35 @@ def build_strategies_ai(
table = Table(title="Accepted Strategies")
table.add_column("#", style="dim")
table.add_column("Strategy", style="cyan")
table.add_column("Factors", style="yellow")
table.add_column("Sharpe", justify="right", style="green")
table.add_column("Monthly %", justify="right", style="green")
table.add_column("Trades", justify="right")
table.add_column("Sharpe", justify="right")
table.add_column("Max DD", justify="right", style="red")
table.add_column("Win Rate", justify="right")
table.add_column("Real IC", justify="right", style="magenta")
table.add_column("Loop", justify="center")
for i, r in enumerate(results, 1):
# Monthly return: use real backtest if available, else estimate
rb = r.get('real_backtest', {})
if isinstance(rb, dict) and rb.get('status') == 'success':
monthly_pct = rb.get('monthly_return_pct', r.get('monthly_return_pct', 0))
n_trades = rb.get('n_trades', '-')
real_ic = rb.get('ic', 0)
else:
monthly_pct = r.get('monthly_return_pct', r.get('real_monthly_return', 0))
n_trades = '-'
real_ic = rb.get('ic', 0) if isinstance(rb, dict) else 0
table.add_row(
str(i),
r.get("strategy_name", "unknown")[:30],
str(len(r.get("factor_names", []))),
f"{r.get('sharpe_ratio', 0):.3f}",
f"{r.get('max_drawdown', 0):.2%}",
f"{r.get('win_rate', 0):.2%}",
f"{monthly_pct:.2f}%",
str(n_trades),
f"{r.get('sharpe', r.get('sharpe_ratio', 0)):.3f}",
f"{r.get('max_drawdown', r.get('est_max_drawdown', 0)):.2%}",
f"{r.get('win_rate', r.get('est_win_rate', 0)):.2%}",
f"{real_ic:.4f}" if real_ic else "-",
str(r.get("loop", "?")),
)
+4
View File
@@ -192,6 +192,10 @@ class QuantRDLoop(RDLoop):
logger.error(f"Factor extraction failed.")
raise FactorEmptyError("Factor extraction failed.")
# Increment factor count for tracking
if hasattr(self, 'trace') and hasattr(self.trace, 'increment_factor_count'):
self.trace.increment_factor_count()
# Handle failed experiments gracefully (don't break the loop)
if getattr(exp, "failed", False):
reason = getattr(exp, "failure_reason", "unknown")
@@ -16,6 +16,15 @@ from rdagent.utils.agent.tpl import T
class QuantTrace(Trace):
def __init__(self, scen: Scenario) -> None:
super().__init__(scen)
self._factor_count = 0
def get_factor_count(self) -> int:
"""Return the number of factors generated so far."""
return self._factor_count
def increment_factor_count(self) -> None:
"""Increment the factor count."""
self._factor_count += 1
# Initialize the controller with default weights
self.controller = EnvController()