feat(agent): improve LLM feedback loop and convergence detection

Three related improvements to help the model learn and explore effectively:

Strategy JSON in history: include the compact strategy JSON in each
IterationRecord::summary() so the LLM knows exactly what was tested in
every past iteration, not just the outcome metrics. Without this the model
had no record of what it tried once conversation history was trimmed.

Rule comment in audit: include rule_comment from the condition audit in
the formatted audit string so the LLM can correlate hit-rate data with
the rule's stated purpose.

Convergence detection and anti-anchoring: diagnose_history() now returns
(String, bool) where the bool signals that the last 3 iterations had
avg_sharpe spread < 0.03 (model stuck in local optimum). When converged:
- Emit a ⚠ CONVERGENCE DETECTED note listing untried candle intervals
- Suppress best_so_far JSON to break the anchoring effect that was
  causing the model to produce near-identical strategies for 13+ iterations
- Targeted "try a different approach" instruction

Also add volume-as-field clarification to the DSL mistakes section in
the system prompt, fixing the "unknown variant `volume`" submit error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 14:38:07 +02:00
parent fb1145acae
commit e27aabae34
3 changed files with 206 additions and 8 deletions

View File

@@ -134,6 +134,8 @@ Common mistakes to NEVER make:
- `rsi`, `adx`, `supertrend` are NOT valid inside `apply_func`. Use only `apply_func`
with `ApplyFuncName` values: `highest`, `lowest`, `sma`, `ema`, `wma`, `std_dev`, `sum`,
`bollinger_upper`, `bollinger_lower`.
- `volume` is a candle FIELD, not a func name. Access it as `{{"kind":"field","field":"volume"}}`.
To compute EMA of volume: `{{"kind":"apply_func","name":"ema","period":20,"expr":{{"kind":"field","field":"volume"}}}}`.
## Working examples
@@ -338,6 +340,7 @@ pub fn iteration_prompt(
iteration: u32,
results_history: &str,
best_so_far: Option<&str>,
diagnosis: &str,
) -> String {
let best_section = match best_so_far {
Some(strat) => format!(
@@ -351,10 +354,11 @@ pub fn iteration_prompt(
};
format!(
r#"Iteration {iteration}. Here are the results from all previous backtests:
r#"Iteration {iteration}. Here are the results from all previous backtests
(each iteration includes the strategy JSON that was tested):
{results_history}
{best_section}
{best_section}{diagnosis}
Based on these results, design the next strategy to test. Learn from what worked
and what didn't. If a strategy family consistently fails, try a different one.