Agent Patterns Hub

Evolutionary Strategy Discovery: How AI Systems Find Winning Approaches

The most powerful AI systems in 2026 are not designed -- they are discovered. QuantEvolve, a system from JP Morgan and the University of Oxford, generates quantitative trading strategies by evolving mathematical formulas. It starts with random expressions, evaluates their fitness against market data, breeds the best performers, and iterates. After hundreds of generations, it produces strategies that outperform human-designed baselines.

ELfolio, from the evolutionary learning literature, applies the same principle to portfolio management -- evolving allocation strategies that adapt to changing market regimes. And in the broader agent economy, teams are discovering that the best orchestration patterns, prompt structures, and tool configurations emerge from population-based search rather than manual engineering.

This is not speculation. These are deployed systems with published results. Here is how evolutionary strategy discovery works, why it outperforms manual design, and how to implement it.

The Core Idea: Evolve, Don't Design

Traditional AI development follows a design-test-iterate loop driven by human intuition. A developer writes a prompt, tests it, tweaks it, and repeats. This works for simple tasks but fails for complex strategy spaces where the number of possible configurations is astronomical.

Evolutionary strategy discovery replaces human intuition with algorithmic search:

EVOLUTIONARY_SEARCH(strategy_space, fitness_function, generations=100):

  # Step 1: Initialize a diverse population
  population = RANDOM_SAMPLE(strategy_space, size=50)

  FOR generation = 1 TO generations:
    # Step 2: Evaluate fitness of each strategy
    FOR each strategy in population:
      strategy.fitness = fitness_function(strategy)

    # Step 3: Select the fittest
    parents = TOURNAMENT_SELECT(population, top_k=20)

    # Step 4: Breed new strategies (crossover + mutation)
    children = []
    FOR i = 0 TO 30:
      parent_a, parent_b = RANDOM_PAIR(parents)
      child = CROSSOVER(parent_a, parent_b)
      child = MUTATE(child, rate=0.1)
      children.append(child)

    # Step 5: Replace worst performers with children
    population = parents + children

    # Step 6: Check for convergence
    IF diversity(population) < threshold: INJECT_RANDOM(population, 5)
    IF best_fitness stable for 10 generations: STOP

  RETURN best_strategy(population)

QuantEvolve: Evolving Trading Formulas

QuantEvolve's innovation is treating trading strategy discovery as a symbolic regression problem. Instead of evolving parameters within a fixed formula (which is what traditional quant does), it evolves the formulas themselves.

A strategy in QuantEvolve is a mathematical expression tree:

// Example evolved formula (simplified)
alpha = rank(correlation(close, volume, 10)) * -1 * sign(delta(close, 3))

// This means:
// 1. Compute 10-day rolling correlation between price and volume
// 2. Rank it across all instruments
// 3. Multiply by the negative sign of 3-day price change
// 4. Result: buy when price-volume correlation is low and price is falling

The key results from the QuantEvolve paper:

  • Diversity of discovered strategies: The system found formulas that human quants had not considered, combining operators in unexpected ways.
  • Regime adaptation: By running evolution continuously, the system discovers strategies suited to the current market regime. When the regime changes, evolution finds new strategies faster than human redesign.
  • Interpretability: Because the output is a symbolic expression (not a neural network), the discovered strategies can be inspected and understood.

ELfolio: Evolving Portfolio Allocations

ELfolio applies evolutionary search to portfolio construction. Instead of using mean-variance optimization (which assumes normally distributed returns and stable correlations), ELfolio evolves allocation strategies that are evaluated on actual historical data, including tail events, regime shifts, and correlation breakdowns.

Each "organism" in ELfolio's population is a portfolio allocation rule:

interface AllocationOrganism {
  // The allocation rule
  rule: {
    signal_weights: Record<string, number>; // weight per signal
    rebalance_trigger: 'weekly' | 'monthly' | 'threshold_based';
    risk_budget: number;                      // max volatility target
    regime_detector: 'none' | 'hmm' | 'momentum';
  };

  // Fitness metrics (measured, not assumed)
  fitness: {
    sharpe_ratio: number;
    max_drawdown: number;
    calmar_ratio: number;
    tail_risk: number;
  };
}

ELfolio's advantage over traditional optimization: it does not assume a model of return distributions. It evaluates strategies against actual market data, including the events that normal distributions say "should never happen." The result is portfolios that survive real-world stress better than Markowitz-optimal portfolios.

Applying Evolutionary Discovery to Agent Systems

The same principle applies to AI agent orchestration. Instead of manually designing prompt templates, tool configurations, and workflow structures, evolve them:

interface AgentConfigOrganism {
  // Prompt strategy
  system_prompt_template: string;
  few_shot_examples: number;
  chain_of_thought: boolean;

  // Model routing
  model_for_planning: 'opus' | 'sonnet' | 'haiku';
  model_for_execution: 'opus' | 'sonnet' | 'haiku';
  model_for_validation: 'opus' | 'sonnet' | 'haiku';

  // Orchestration
  max_retries: number;
  quality_gate_threshold: number;
  decomposition_depth: number;
  fan_out: number;

  // Cost
  budget_per_task: number;
  context_pruning_strategy: 'none' | 'summarize' | 'extract_types';
}

// Fitness function: run the agent config on a benchmark suite
function evaluateAgentConfig(config: AgentConfigOrganism): number {
  const results = benchmarkSuite.map(task => {
    const result = runAgent(task, config);
    return {
      quality: result.quality_score,     // 0-100
      cost: result.total_cost,           // in dollars
      latency: result.total_seconds,     // wall-clock time
    };
  });

  // Multi-objective fitness
  const avgQuality = mean(results.map(r => r.quality));
  const avgCost = mean(results.map(r => r.cost));
  const avgLatency = mean(results.map(r => r.latency));

  // Weighted composite (customize per use case)
  return avgQuality * 0.5 - avgCost * 10 * 0.3 - avgLatency * 0.2;
}

The Diversity Imperative

Evolutionary search has a known failure mode: premature convergence. The population collapses to copies of the current best strategy, losing the diversity needed to find better strategies in unexplored regions of the space.

Three mechanisms prevent this:

  1. Niche protection: Strategies that are the sole representative of a configuration type get selection protection even if their fitness is below average.
  2. Adaptive mutation rate: When population diversity drops, mutation rate increases automatically. When diversity is healthy, mutation rate decreases.
  3. Anti-correlation selection: When selecting survivors, penalize pairs of strategies whose performance is highly correlated. Two strategies that win and lose on the same tasks provide no portfolio benefit.
// Effective diversity: how many truly independent strategies do you have?
const effective_n = N / (1 + (N - 1) * mean_pairwise_correlation);

// If N=10 but correlation=0.9: effective_n = 1.1
// Ten strategies, but only 1.1 independent viewpoints.

// Diversity floor: if effective_n drops below 3, force diversification
if (effective_n < 3) {
  injectRandomStrategies(population, count=5);
  increaseMutationRate(2.0);
}

The Graveyard: Learning From Dead Strategies

In biological evolution, extinctions carry information. The same is true for strategy evolution. When a strategy is culled, its post-mortem data is invaluable:

  • What market conditions killed it?
  • What was its peak fitness, and when did it start declining?
  • Are there surviving strategies with similar traits that might die next?
  • What did the dead strategy do well that could be transplanted to survivors?

A strategy graveyard is a searchable database of dead strategies. When a new strategy is born, it queries the graveyard: "Have strategies like me been tried before? What killed them? What would have saved them?"

This prevents the population from rediscovering strategies that were already tried and found wanting. It is organizational memory for an evolutionary system.

Implementation: A Minimal Evolutionary Agent Optimizer

class EvolutionaryOptimizer {
  private population: AgentConfigOrganism[] = [];
  private graveyard: GraveyardEntry[] = [];
  private generation = 0;

  constructor(
    private populationSize: number = 20,
    private mutationRate: number = 0.15,
    private eliteCount: number = 5,
  ) {}

  async initialize(): Promise<void> {
    this.population = Array.from({ length: this.populationSize }, () =>
      this.randomConfig()
    );
  }

  async evolve(generations: number): Promise<AgentConfigOrganism> {
    for (let gen = 0; gen < generations; gen++) {
      // Evaluate
      for (const org of this.population) {
        org.fitness = await evaluateAgentConfig(org);
      }

      // Sort by fitness
      this.population.sort((a, b) => b.fitness - a.fitness);

      // Record statistics
      console.log("Gen " + gen + ": best=" +
        this.population[0].fitness.toFixed(2) + ", worst=" +
        this.population[this.population.length-1].fitness.toFixed(2));

      // Cull bottom performers (bury in graveyard first)
      const culled = this.population.slice(this.eliteCount);
      for (const dead of culled) {
        this.graveyard.push({ config: dead, generation: gen, cause: 'low_fitness' });
      }

      // Keep elite
      const elite = this.population.slice(0, this.eliteCount);

      // Breed new population
      const children: AgentConfigOrganism[] = [];
      while (children.length < this.populationSize - this.eliteCount) {
        const [a, b] = this.tournamentSelect(elite, 2);
        let child = this.crossover(a, b);
        child = this.mutate(child);
        children.push(child);
      }

      this.population = [...elite, ...children];
      this.generation = gen;
    }

    return this.population[0]; // Return best
  }
}

When to Use Evolutionary Strategy Discovery

Use WhenDon't Use When
Strategy space is large (100+ parameters)Strategy space is small (< 10 parameters)
Fitness function is cheap to evaluateEach evaluation costs $10+ (budget exhausted in one generation)
Optimal strategy is unknown or counterintuitiveBest practice is well-established
Environment changes (need continuous adaptation)Environment is static (one-time optimization suffices)
Multiple competing objectives (quality vs cost vs speed)Single clear objective with known solution

Practical Checklist

  1. Define a fitness function. The single most important decision. If you cannot measure strategy quality automatically, you cannot evolve strategies.
  2. Start with a diverse initial population. Random initialization covers more of the search space than variations on a single design.
  3. Enforce diversity preservation. Without it, the population converges to a monoculture within 5-10 generations.
  4. Maintain a graveyard. Every dead strategy has lessons. Reuse what worked; avoid what killed it.
  5. Set a budget per generation. Evolution can be expensive. Cap the cost per evaluation and per generation.
  6. Run continuously. The best strategies for today's environment may be the worst for tomorrow's. Continuous evolution adapts in real time.

This approach is one of 15 patterns in the Protocol Playbook, which covers the complete set of orchestration patterns for production AI agent systems -- from task decomposition to graveyard learning.