from __future__ import annotations import random from typing import Any from .hypothesis import HypothesisAgentGenome FEATURE_POOL: tuple[str, ...] = ("open", "high", "low", "close", "volume") COGNITIVE_STYLES: tuple[str, ...] = ( "physicist", "biologist", "historian", "meteorologist", "ecologist", "engineer", ) def _clone_with(g: HypothesisAgentGenome, **overrides: Any) -> HypothesisAgentGenome: payload: dict[str, Any] = g.to_dict() payload.update(overrides) payload.pop("id", None) payload["parent_ids"] = [*g.parent_ids, g.id] payload["generation"] = g.generation + 1 return HypothesisAgentGenome.from_dict(payload) def mutate_temperature(g: HypothesisAgentGenome, rng: random.Random) -> HypothesisAgentGenome: delta = rng.choice([-0.1, 0.1]) new_t = max(0.6, min(1.3, g.temperature + delta)) return _clone_with(g, temperature=round(new_t, 4)) def mutate_lookback(g: HypothesisAgentGenome, rng: random.Random) -> HypothesisAgentGenome: delta = rng.choice([-50, 50]) new_lb = max(50, min(500, g.lookback_window + delta)) return _clone_with(g, lookback_window=new_lb) def mutate_feature_access(g: HypothesisAgentGenome, rng: random.Random) -> HypothesisAgentGenome: current = set(g.feature_access) if len(current) == len(FEATURE_POOL): op = "remove" elif len(current) <= 1: op = "add" else: op = rng.choice(["add", "remove"]) if op == "add": candidates = [f for f in FEATURE_POOL if f not in current] choice = rng.choice(candidates) new_set = current | {choice} else: choice = rng.choice(sorted(current)) new_set = current - {choice} return _clone_with(g, feature_access=sorted(new_set)) def mutate_cognitive_style(g: HypothesisAgentGenome, rng: random.Random) -> HypothesisAgentGenome: candidates = [s for s in COGNITIVE_STYLES if s != g.cognitive_style] new_style = rng.choice(candidates) return _clone_with(g, cognitive_style=new_style) MUTATION_OPS = ( mutate_temperature, mutate_lookback, mutate_feature_access, mutate_cognitive_style, ) def random_mutate(g: HypothesisAgentGenome, rng: random.Random) -> HypothesisAgentGenome: op = rng.choice(MUTATION_OPS) return op(g, rng)