fix: dashboard goes silent during Phase 1 (no AI thinking, empty Param Changes panel)
Three issues caused the dashboard to look frozen during the long Phase 1 exploration phase, even when 2+ runs had completed: 1. AI Thinking feed only got ONE entry (the phase-start banner) and then went silent until Phase 2. Phase 1 is LHS sampling so there are no AI calls — but we can still narrate observations rule-based. Added _narrate_phase1_run() that emits a per-result message tuned to what actually happened: 'PF 1.8, DD 14% — strong region, worth refining' (success), '$-734 loss with 191 trades — skipping' (warning), 'only 3 trades — params too restrictive' (info), etc. Mediocre runs are throttled to 1-in-5 to avoid feed spam 2. Parameter Changes panel showed 'No iterations yet — AI-driven param edits appear per iteration' which is misleading during Phase 1 (where there are no iterations YET, by design). Empty-state message now phase-aware: shows 'Exploration phase — changes appear in Phase 2' during phase1, 'Waiting for first AI iteration' if autonomous, or 'Random-neighbor refinement — enable Autonomous AI for AI changes' if not autonomous 3. AI Summary still said 'Waiting for optimization to start...' even while the optimization was actively running. New refreshAIWaitingState() updates the message based on (a) whether a run is active and (b) whether the API key is set. When running with no key, shows 'AI insights are off — no Anthropic API key set' with a 'Configure API key in Settings →' link Frontend now also tracks state.phase and state.autonomous from phase_start events so all empty-state messages stay in sync.
This commit is contained in:
@@ -312,6 +312,10 @@ class OptimizationPipeline:
|
||||
"budget_summary": budget.summary(),
|
||||
})
|
||||
|
||||
# Narrate every Phase 1 outcome so the AI Thinking feed stays alive
|
||||
# even though no API call is made — this is observation, not AI reasoning
|
||||
self._narrate_phase1_run(i + 1, cfg.phase1_samples, result)
|
||||
|
||||
if budget.is_exhausted():
|
||||
self._log("warning", "⏱ Time budget reached during Phase 1")
|
||||
break
|
||||
@@ -1126,6 +1130,66 @@ class OptimizationPipeline:
|
||||
self._thinking_log = self._thinking_log[-200:]
|
||||
self._emit("ai_thinking", payload)
|
||||
|
||||
def _narrate_phase1_run(self, idx: int, total: int, result: RankedResult) -> None:
|
||||
"""
|
||||
Emit a short observation per Phase 1 run so the AI Thinking feed
|
||||
stays alive during the LHS exploration phase. No API call — pure
|
||||
rule-based narration of what just happened.
|
||||
|
||||
Cadence:
|
||||
* Every passing result → success message
|
||||
* Every clearly bad result → warning
|
||||
* Once every 5 mediocre runs → progress beat
|
||||
"""
|
||||
try:
|
||||
pf = float(getattr(result, "profit_factor", 0) or 0)
|
||||
dd = float(getattr(result, "max_drawdown", 0) or 0) # fraction
|
||||
tr = int(getattr(result, "total_trades", 0) or 0)
|
||||
np_ = float(getattr(result, "net_profit", 0) or 0)
|
||||
passing = bool(getattr(result, "passing", False))
|
||||
|
||||
tag = f"Run {idx}/{total}"
|
||||
|
||||
if tr == 0 or tr < 10:
|
||||
msg = (
|
||||
f"{tag}: only {tr} trades — params likely too restrictive "
|
||||
f"(session window, signal threshold, or risk filter blocking entries)."
|
||||
)
|
||||
kind = "info"
|
||||
elif passing and pf >= 1.5 and dd * 100 <= 15:
|
||||
msg = (
|
||||
f"{tag}: PF {pf:.2f}, DD {dd*100:.1f}%, {tr} trades — "
|
||||
f"strong region, worth refining."
|
||||
)
|
||||
kind = "success"
|
||||
elif passing:
|
||||
msg = (
|
||||
f"{tag}: PF {pf:.2f}, DD {dd*100:.1f}% — passes gates but "
|
||||
f"not a standout. Logged."
|
||||
)
|
||||
kind = "info"
|
||||
elif np_ < 0:
|
||||
msg = (
|
||||
f"{tag}: ${np_:.0f} loss with {tr} trades. Skipping this region."
|
||||
)
|
||||
kind = "warning"
|
||||
elif dd * 100 > 25:
|
||||
msg = (
|
||||
f"{tag}: drawdown {dd*100:.1f}% too high — EA over-leveraged "
|
||||
f"in this parameter region."
|
||||
)
|
||||
kind = "warning"
|
||||
else:
|
||||
# Mediocre — only emit one in five so we don't spam
|
||||
if idx % 5 != 0:
|
||||
return
|
||||
msg = f"{tag}: PF {pf:.2f}, DD {dd*100:.1f}% — mediocre, moving on."
|
||||
kind = "info"
|
||||
|
||||
self._emit_thinking(msg, kind=kind)
|
||||
except Exception as e:
|
||||
logger.debug(f"_narrate_phase1_run failed: {e}")
|
||||
|
||||
def _emit_early_termination(self, reason_code: str, message: str, details: dict = None) -> None:
|
||||
"""
|
||||
Surface an early stop to the user.
|
||||
|
||||
Reference in New Issue
Block a user