From 5202eb517ba200290546009dc30b2030eb782825 Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Fri, 15 May 2026 21:18:54 +0000 Subject: [PATCH] =?UTF-8?q?refactor(prompt):=20SYSTEM=5FTEMPLATE=20diventa?= =?UTF-8?q?=20compositor=20=E2=80=94=20strategy=20controlla=20agent=5Frole?= =?UTF-8?q?/pattern=5Fguidance/instruction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor architetturale: il prompt non e' piu' un template hardcoded ma viene COMPOSTO at-runtime da scaffold core + contenuto strategy. CORE scaffold (universal, fisso): - Grammar JSON spec (legato al protocol/compiler) - UNITA' regole semantiche - VINCOLI del validator - Esempio output STRATEGY content (tunable in prompts.json): - agent_role: "Sei un agente generatore di ipotesi ... [crypto/forex/equity]" - pattern_guidance: sezione di archetipi tecnici, ora crypto-specific - instruction: frase finale del USER ("Genera una strategia ... [crypto]") - domain_warnings: NEW opzionale, per disclaimer di dominio (es. crypto 24/7) Implementazione: - PromptLibrary v3.0: 4 nuovi campi top-level (agent_role, pattern_guidance, instruction, domain_warnings), parsati da prompts.json, default fallback in default() - hypothesis.py: SYSTEM_TEMPLATE constant rimossa, sostituita da _build_system_prompt(lib, genome) che compone scaffold + content - USER_TEMPLATE: ultima riga ora ha placeholder {instruction} - prompts.json v3.0 in strategy_crypto: agent_role + pattern_guidance + instruction + domain_warnings popolati con flavor crypto-specific Pattern: "core = framework, strategy = contenuto". Per future strategie (forex, equity), basta creare un prompts.json con flavor diverso, zero modifiche al core. Universal scaffold (grammar, vincoli, units) e' condiviso e mantiene la garanzia di parse/validate. Test: +5 unit (compositor + PromptLibrary fields). Tot: 232 test pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../multi_swarm_core/agents/hypothesis.py | 82 ++++++++++------- .../multi_swarm_core/genome/prompt_library.py | 90 +++++++++++++++++-- .../tests/unit/test_hypothesis_agent.py | 71 +++++++++++++++ .../tests/unit/test_prompt_library.py | 78 ++++++++++++++++ .../strategy_crypto/prompts.json | 17 +++- 5 files changed, 294 insertions(+), 44 deletions(-) create mode 100644 src/multi_swarm_core/tests/unit/test_prompt_library.py diff --git a/src/multi_swarm_core/multi_swarm_core/agents/hypothesis.py b/src/multi_swarm_core/multi_swarm_core/agents/hypothesis.py index 31bb3fa..0c3a31f 100644 --- a/src/multi_swarm_core/multi_swarm_core/agents/hypothesis.py +++ b/src/multi_swarm_core/multi_swarm_core/agents/hypothesis.py @@ -56,12 +56,9 @@ class HypothesisProposal: n_attempts: int = 1 -SYSTEM_TEMPLATE = """\ -Sei un agente generatore di ipotesi di trading quantitativo per un sistema swarm. - -Il tuo stile cognitivo: {cognitive_style} -Direttiva personale: {system_prompt} +# === CORE SCAFFOLD constants (universal, legato al protocol/compiler) === +_SYSTEM_GRAMMAR_SPEC = """\ Devi proporre una strategia di trading espressa in JSON STRETTO. La risposta deve essere un singolo oggetto JSON dentro fence ```json...``` con questa shape: @@ -136,35 +133,18 @@ Esempi di gating temporale: {{"op": "eq", "args": [{{"kind": "feature", "name": "is_weekend"}}, {{"kind": "literal", "value": 1}}]}} Leaf - letterale numerico: - {{"kind": "literal", "value": 70.0}} - -PATTERN GUIDANCE (oltre agli indicatori, considera forma delle curve e ripetibilità): - - Forme di curva: - - Trend ascendente: SMA(short) > SMA(long) E close > SMA(short) - - Trend discendente: SMA(short) < SMA(long) E close < SMA(short) - - Compressione di volatilità (pre-breakout): atr_pct(N) < 0.01 (sotto 1% del prezzo) - - Espansione di volatilità: atr_pct(N) > 0.03 (sopra 3%) OPPURE ATR(N) > ATR(N*2) confronto relativo - - Mean reversion strutturale: sma_pct(long) > 0.05 (close 5% sopra media) OR sma_pct(long) < -0.05 - - Momentum positivo conferma: macd_pct(12,26,9) > 0.005 (> 0.5% del prezzo) - - Ripetibilità dell'andamento: - - Eventi crossover/crossunder ricorrenti (golden/death cross, RSI cross zone) - - Pattern intra-day: usa 'hour' per sfruttare orari di forte volatilità ricorrente - - Pattern settimanali: usa 'dow' o 'is_weekend' per cicli mercato - - Doppio top approx: RSI > 70 + crossunder RSI 70 (1° picco), poi entro N bar - nuovo crossover RSI 70 a livello close simile → entry short - - Range breakout: close > SMA(N) con SMA(short) > SMA(long) (compressione + spinta) - - Cerca pattern che si REPLICANO nei dati storici, non singoli eventi rari. + {{"kind": "literal", "value": 70.0}}""" +_SYSTEM_CONSTRAINTS = """\ VINCOLI - Gli indicator NON sono annidabili: 'params' accetta solo numeri, mai altri nodi. - Le regole sono valutate in ordine; la prima che matcha vince per ogni timestamp. - Default action se nessuna regola matcha = flat. - 'op' e 'kind' sono mutuamente esclusivi sullo stesso nodo. -Rispondi SOLO con il fence ```json...``` contenente l'oggetto strategy. +Rispondi SOLO con il fence ```json...``` contenente l'oggetto strategy.""" + +_SYSTEM_EXAMPLE = """\ Esempio: ```json @@ -186,8 +166,40 @@ Esempio: }} ] }} -``` -""" +```""" + + +def _build_system_prompt(lib: PromptLibrary, genome: HypothesisAgentGenome) -> str: + """Compone il SYSTEM prompt da scaffold core + contenuto strategy-specific.""" + parts: list[str] = [] + # 1. Header strategy-specific (da prompts.json) + parts.append(lib.agent_role) + parts.append("") + # 2. Cognitive style + directive (sempre, dal genome) + parts.append(f"Il tuo stile cognitivo: {genome.cognitive_style}") + parts.append(f"Direttiva personale: {genome.system_prompt}") + parts.append("") + # 3. Grammar spec (core scaffold) + parts.append(_SYSTEM_GRAMMAR_SPEC) + # 4. Pattern guidance (da prompts.json, opzionale) + if lib.pattern_guidance: + parts.append( + "\nPATTERN GUIDANCE (oltre agli indicatori, considera forma delle curve e ripetibilità):\n" + ) + parts.append(lib.pattern_guidance) + parts.append( + "\n Cerca pattern che si REPLICANO nei dati storici, non singoli eventi rari.\n" + ) + # 5. Domain warnings (da prompts.json, opzionale) + if lib.domain_warnings: + parts.append("\nWARNING DI DOMINIO:\n") + parts.append(lib.domain_warnings) + parts.append("") + # 6. Vincoli (core scaffold) + parts.append(_SYSTEM_CONSTRAINTS) + # 7. Esempio (core scaffold) + parts.append(_SYSTEM_EXAMPLE) + return "\n".join(parts) USER_TEMPLATE = """\ @@ -213,7 +225,7 @@ Geometria & frattali: Feature accessibili dal tuo genoma: {feature_access}. Lookback massimo che puoi usare nel ragionamento: {lookback_window} barre. -Genera una strategia che cerchi anomalie sfruttabili in questo regime. +{instruction} """ @@ -361,10 +373,7 @@ class HypothesisAgent: genome: HypothesisAgentGenome, market: MarketSummary, ) -> HypothesisProposal: - system = SYSTEM_TEMPLATE.format( - cognitive_style=genome.cognitive_style, - system_prompt=genome.system_prompt, - ) + system = _build_system_prompt(self._prompt_library, genome) dominant_cycle_str = ( f"{market.dominant_cycle:.0f} barre" if market.dominant_cycle is not None @@ -372,6 +381,10 @@ class HypothesisAgent: ) focus_keys = self._prompt_library.focus_metrics_for(genome.cognitive_style) focus_block = _render_focus_block(focus_keys, market) if focus_keys else "" + instruction = ( + self._prompt_library.instruction + or "Genera una strategia che cerchi anomalie sfruttabili in questo regime." + ) original_user = USER_TEMPLATE.format( symbol=market.symbol, timeframe=market.timeframe, @@ -396,6 +409,7 @@ class HypothesisAgent: dominant_cycle_str=dominant_cycle_str, feature_access=", ".join(genome.feature_access), lookback_window=genome.lookback_window, + instruction=instruction, ) + focus_block completions: list[CompletionResult] = [] diff --git a/src/multi_swarm_core/multi_swarm_core/genome/prompt_library.py b/src/multi_swarm_core/multi_swarm_core/genome/prompt_library.py index f6447f1..46fcfcc 100644 --- a/src/multi_swarm_core/multi_swarm_core/genome/prompt_library.py +++ b/src/multi_swarm_core/multi_swarm_core/genome/prompt_library.py @@ -15,7 +15,11 @@ Schema JSON atteso:: "styles": { "": {"directive": ""}, ... - } + }, + "agent_role": "", + "pattern_guidance": "", + "instruction": "", + "domain_warnings": "" } I 6 stili default (physicist, biologist, historian, meteorologist, @@ -26,7 +30,7 @@ per backcompat con test/script senza file esterno. from __future__ import annotations import json -from dataclasses import dataclass +from dataclasses import dataclass, field from pathlib import Path _DEFAULT_STYLES: dict[str, str] = { @@ -61,12 +65,39 @@ class PromptLibraryError(ValueError): """Sollevata su JSON malformato o stili invalid.""" +_DEFAULT_PATTERN_GUIDANCE = ( + "Forme di curva: trend (SMA cross), compressione volatilita (atr_pct basso), " + "espansione volatilita (atr_pct alto), mean reversion (sma_pct estremo), " + "momentum (macd_pct, rsi zone).\n\n" + " Ripetibilita dell'andamento: crossover/crossunder ricorrenti, pattern intra-day " + "(hour gate), pattern settimanali (dow/is_weekend), range breakout." +) + +_DEFAULT_AGENT_ROLE = ( + "Sei un agente generatore di ipotesi di trading quantitativo per un sistema swarm." +) + +_DEFAULT_INSTRUCTION = ( + "Genera una strategia che cerchi anomalie sfruttabili in questo regime." +) + + @dataclass(frozen=True) class PromptLibrary: - """Set immutabile di stili cognitivi + direttive system_prompt.""" + """Set immutabile di stili cognitivi + direttive system_prompt. + + v3.0: aggiunge 4 campi top-level strategy-specific iniettabili nel prompt + dal compositor ``_build_system_prompt``. Tutti opzionali con default sensati + per backcompat. + """ styles: dict[str, str] focus: dict[str, list[str]] # style -> lista metriche prioritarie + # NEW v3.0: contenuto strategy-specific iniettabile nel prompt + agent_role: str = field(default="") # header SYSTEM, descrive chi e' l'agente + pattern_guidance: str = field(default="") # sezione PATTERN GUIDANCE del SYSTEM + instruction: str = field(default="") # frase finale USER ("Genera una strategia...") + domain_warnings: str = field(default="") # opzionale: warning di dominio (es. crypto 24/7) def __post_init__(self) -> None: if not self.styles: @@ -78,15 +109,48 @@ class PromptLibrary: raise PromptLibraryError( f"directive vuota o invalida per stile {name!r}" ) + # Validate new optional string fields: if provided must be non-whitespace + for field_name, value in ( + ("agent_role", self.agent_role), + ("pattern_guidance", self.pattern_guidance), + ("instruction", self.instruction), + ("domain_warnings", self.domain_warnings), + ): + if not isinstance(value, str): + raise PromptLibraryError( + f"campo '{field_name}' deve essere stringa, non {type(value)}" + ) + if value and not value.strip(): + raise PromptLibraryError( + f"campo '{field_name}' fornito ma contiene solo whitespace" + ) @classmethod def default(cls) -> PromptLibrary: """Libreria builtin con i 6 stili originali (fallback senza file).""" - return cls(styles=dict(_DEFAULT_STYLES), focus={}) + return cls( + styles=dict(_DEFAULT_STYLES), + focus={}, + agent_role=_DEFAULT_AGENT_ROLE, + pattern_guidance=_DEFAULT_PATTERN_GUIDANCE, + instruction=_DEFAULT_INSTRUCTION, + domain_warnings="", + ) @classmethod def from_json(cls, path: Path | str) -> PromptLibrary: - """Carica da file JSON con schema ``{"styles": {: {"directive": "...", "focus_metrics": [...]}}}``.""" + """Carica da file JSON. + + Schema:: + + { + "styles": {: {"directive": "...", "focus_metrics": [...]}}, + "agent_role": "...", // opzionale + "pattern_guidance": "...", // opzionale + "instruction": "...", // opzionale + "domain_warnings": "..." // opzionale + } + """ p = Path(path) try: data = json.loads(p.read_text(encoding="utf-8")) @@ -120,7 +184,21 @@ class PromptLibrary: f"focus_metrics di {name!r} deve essere lista, non {type(fm)}" ) focus[name] = [str(x) for x in fm] - return cls(styles=styles, focus=focus) + + # Parse new optional top-level fields (v3.0) + agent_role = data.get("agent_role", "") + pattern_guidance = data.get("pattern_guidance", "") + instruction = data.get("instruction", "") + domain_warnings = data.get("domain_warnings", "") + + return cls( + styles=styles, + focus=focus, + agent_role=agent_role, + pattern_guidance=pattern_guidance, + instruction=instruction, + domain_warnings=domain_warnings, + ) @property def cognitive_styles(self) -> tuple[str, ...]: diff --git a/src/multi_swarm_core/tests/unit/test_hypothesis_agent.py b/src/multi_swarm_core/tests/unit/test_hypothesis_agent.py index 11d04d0..bc3f88c 100644 --- a/src/multi_swarm_core/tests/unit/test_hypothesis_agent.py +++ b/src/multi_swarm_core/tests/unit/test_hypothesis_agent.py @@ -299,3 +299,74 @@ def test_propose_no_focus_block_when_style_not_in_library(mocker): # type: igno call_kwargs = fake_llm.complete.call_args.kwargs user_msg = call_kwargs["user"] assert "Focus per la tua lente:" not in user_msg + + +def test_build_system_prompt_includes_role_and_guidance_from_library(mocker): # type: ignore[no-untyped-def] + """agent_role e pattern_guidance da PromptLibrary appaiono nel SYSTEM prompt.""" + fake_llm = mocker.MagicMock() + fake_llm.complete.return_value = CompletionResult( + text=VALID_STRATEGY_JSON, + input_tokens=200, + output_tokens=80, + tier=ModelTier.C, + model="qwen", + ) + lib = PromptLibrary( + styles={"physicist": "Pensa come un fisico."}, + focus={}, + agent_role="ROLE_X", + pattern_guidance="GUIDE_X", + ) + agent = HypothesisAgent(llm=fake_llm, prompt_library=lib) + agent.propose(make_genome(), make_summary()) + call_kwargs = fake_llm.complete.call_args.kwargs + system_msg = call_kwargs["system"] + assert "ROLE_X" in system_msg + assert "GUIDE_X" in system_msg + + +def test_build_system_prompt_skips_empty_optional_sections(mocker): # type: ignore[no-untyped-def] + """domain_warnings='' e pattern_guidance='' → sezioni opzionali assenti nel SYSTEM.""" + fake_llm = mocker.MagicMock() + fake_llm.complete.return_value = CompletionResult( + text=VALID_STRATEGY_JSON, + input_tokens=200, + output_tokens=80, + tier=ModelTier.C, + model="qwen", + ) + lib = PromptLibrary( + styles={"physicist": "Pensa come un fisico."}, + focus={}, + domain_warnings="", + pattern_guidance="", + ) + agent = HypothesisAgent(llm=fake_llm, prompt_library=lib) + agent.propose(make_genome(), make_summary()) + call_kwargs = fake_llm.complete.call_args.kwargs + system_msg = call_kwargs["system"] + assert "WARNING DI DOMINIO" not in system_msg + assert "PATTERN GUIDANCE" not in system_msg + + +def test_user_template_uses_instruction_from_library(mocker): # type: ignore[no-untyped-def] + """instruction da PromptLibrary appare nel USER message; default non viene usato.""" + fake_llm = mocker.MagicMock() + fake_llm.complete.return_value = CompletionResult( + text=VALID_STRATEGY_JSON, + input_tokens=200, + output_tokens=80, + tier=ModelTier.C, + model="qwen", + ) + lib = PromptLibrary( + styles={"physicist": "Pensa come un fisico."}, + focus={}, + instruction="INSTR_X", + ) + agent = HypothesisAgent(llm=fake_llm, prompt_library=lib) + agent.propose(make_genome(), make_summary()) + call_kwargs = fake_llm.complete.call_args.kwargs + user_msg = call_kwargs["user"] + assert "INSTR_X" in user_msg + assert "Genera una strategia che cerchi anomalie sfruttabili in questo regime." not in user_msg diff --git a/src/multi_swarm_core/tests/unit/test_prompt_library.py b/src/multi_swarm_core/tests/unit/test_prompt_library.py new file mode 100644 index 0000000..86485af --- /dev/null +++ b/src/multi_swarm_core/tests/unit/test_prompt_library.py @@ -0,0 +1,78 @@ +"""Unit tests per PromptLibrary v3.0 — nuovi campi top-level.""" + +from __future__ import annotations + +import json +import tempfile +from pathlib import Path + +import pytest + +from multi_swarm_core.genome.prompt_library import PromptLibrary, PromptLibraryError + + +def _write_json(data: dict, tmp_path: Path) -> Path: + p = tmp_path / "prompts.json" + p.write_text(json.dumps(data), encoding="utf-8") + return p + + +def test_from_json_loads_new_top_level_fields(tmp_path: Path) -> None: + """from_json() legge agent_role, pattern_guidance, instruction, domain_warnings.""" + data = { + "styles": {"physicist": {"directive": "Cerca leggi conservative."}}, + "agent_role": "Sei un agente test.", + "pattern_guidance": "Forme di curva: trend, compressione.", + "instruction": "Genera qualcosa di utile.", + "domain_warnings": "Attenzione: mercato 24/7.", + } + lib = PromptLibrary.from_json(_write_json(data, tmp_path)) + assert lib.agent_role == "Sei un agente test." + assert lib.pattern_guidance == "Forme di curva: trend, compressione." + assert lib.instruction == "Genera qualcosa di utile." + assert lib.domain_warnings == "Attenzione: mercato 24/7." + + +def test_from_json_defaults_new_fields_when_absent(tmp_path: Path) -> None: + """from_json() usa stringa vuota come default per i 4 campi opzionali.""" + data = { + "styles": {"physicist": {"directive": "Cerca leggi conservative."}}, + } + lib = PromptLibrary.from_json(_write_json(data, tmp_path)) + assert lib.agent_role == "" + assert lib.pattern_guidance == "" + assert lib.instruction == "" + assert lib.domain_warnings == "" + + +def test_default_provides_universal_fallbacks() -> None: + """PromptLibrary.default() ha agent_role, pattern_guidance, instruction non vuoti; domain_warnings vuoto.""" + lib = PromptLibrary.default() + assert lib.agent_role != "" + assert lib.pattern_guidance != "" + assert lib.instruction != "" + assert lib.domain_warnings == "" + + +def test_prompt_library_rejects_whitespace_only_fields() -> None: + """Campi opzionali forniti ma solo whitespace sollevano PromptLibraryError.""" + with pytest.raises(PromptLibraryError, match="agent_role"): + PromptLibrary( + styles={"physicist": "Cerca leggi."}, + focus={}, + agent_role=" ", + ) + + +def test_prompt_library_accepts_empty_string_for_optional_fields() -> None: + """Stringa vuota '' e' accettata per tutti i campi opzionali.""" + lib = PromptLibrary( + styles={"physicist": "Cerca leggi."}, + focus={}, + agent_role="", + pattern_guidance="", + instruction="", + domain_warnings="", + ) + assert lib.agent_role == "" + assert lib.domain_warnings == "" diff --git a/src/strategy_crypto/strategy_crypto/prompts.json b/src/strategy_crypto/strategy_crypto/prompts.json index 4b3196e..aa817f2 100644 --- a/src/strategy_crypto/strategy_crypto/prompts.json +++ b/src/strategy_crypto/strategy_crypto/prompts.json @@ -1,9 +1,18 @@ { - "_comment": "Stili cognitivi e direttive del system_prompt per il GA di strategy_crypto. Modifica liberamente: cambia 'directive' di uno stile esistente o aggiungi nuove voci a 'styles'. Il nome dello stile (key) viene usato come 'cognitive_style' del genoma; la 'directive' diventa il system_prompt iniziale.", - "_schema": "2.2", - "_changelog": "v2.2 - Aggiunte 5 metriche geometrico-frattali (efficiency_ratio, tail_index, structural_uptrend, compression, spectral_entropy+dominant_cycle). Ogni stile dichiara focus_metrics: lista delle metriche prioritarie renderizzate come 'Focus per la tua lente' nel USER_TEMPLATE. Le metriche restano visibili a tutti gli stili (landscape GA smooth); il focus_metrics e' enfasi, non filtering. v3.0 - Riscrittura per accentuare differenziazione cognitiva. Ogni directive ora ha: (1) metafora ancorante riconoscibile e robusta a mutazione, (2) interpretazione operativa dei 6 input (skew/kurt/AR/Hurst/vol_pct/seasonality), (3) hint di struttura grammar-aware (and/or/crossover) senza prescrivere indicatori, (4) bias di scala temporale (lookback breve/medio/lungo), (5) archetipo dominante distintivo. Linguaggio attivo/generativo invece di 'preferisci'. ASCII-safe rigoroso. v2.1 - directive estese con interpretazione dei 4 nuovi input statistici. v2.0 - Riprogettato per blind-generator GA, 7 lenti mappate sulle statistiche aggregate.", - "_design_notes": "Le directive sono BIAS DI ESPLORAZIONE, non template. Ogni lente ha una METAFORA DOMINANTE (es. physicist = sistema fisico; biologist = ecosistema) che funge da ancora semantica resistente alle riscritture di mutate_prompt_llm. Gli hint di struttura grammar-aware orientano la FORMA della strategia (numero condizioni, tipo combinazione) senza prescrivere quali indicatori usare: il GA scopre l'incarnazione. Gli hint di scala temporale producono bias naturali sul lookback_window (parametro evoluto). Differenziazione: ogni lente ha un ARCHETIPO DOMINANTE distinto (physicist=mean-reversion+momentum simmetrico; biologist=contrarian su crowding; historian=cicli e fasi; meteorologist=regime-switching; engineer=robustezza+SNR; military_strategist=offesa/difesa adattiva; psychologist=fade estremi emotivi).", + "_comment": "Stili cognitivi e direttive del system_prompt per il GA di strategy_crypto. Modifica liberamente: cambia 'directive' di uno stile esistente o aggiungi nuove voci a 'styles'. Il nome dello stile (key) viene usato come 'cognitive_style' del genoma; la 'directive' diventa il system_prompt iniziale. v3.0: aggiunti campi top-level agent_role, pattern_guidance, instruction, domain_warnings per il compositor del SYSTEM prompt.", + "_schema": "3.0", + "_changelog": "v2.2 - Aggiunte 5 metriche geometrico-frattali (efficiency_ratio, tail_index, structural_uptrend, compression, spectral_entropy+dominant_cycle). Ogni stile dichiara focus_metrics: lista delle metriche prioritarie renderizzate come 'Focus per la tua lente' nel USER_TEMPLATE. Le metriche restano visibili a tutti gli stili (landscape GA smooth); il focus_metrics e' enfasi, non filtering. v2.1 - directive estese con interpretazione dei 4 nuovi input statistici. v2.0 - Riprogettato per blind-generator GA, 7 lenti mappate sulle statistiche aggregate. Stile precedente v3.0 changelog delle directive (differenziazione cognitiva) ora riflesso internamente nelle directive stesse. v3.0 - Refactor compositore: prompts.json ora controlla agent_role, pattern_guidance, instruction (top-level). Core fornisce solo lo SCAFFOLD universale (grammar JSON spec, units rules, vincoli del validator, esempio output). Pattern del core: 'core = framework, strategy = contenuto'. Strategy puo' tunare interamente il flavor del prompt senza toccare codice.", + "_design_notes": "Le directive sono BIAS DI ESPLORAZIONE, non template. Ogni lente ha una METAFORA DOMINANTE (es. physicist = sistema fisico; biologist = ecosistema) che funge da ancora semantica resistente alle riscritture di mutate_prompt_llm. Gli hint di struttura grammar-aware orientano la FORMA della strategia (numero condizioni, tipo combinazione) senza prescrivere quali indicatori usare: il GA scopre l'incarnazione. Gli hint di scala temporale producono bias naturali sul lookback_window (parametro evoluto). Differenziazione: ogni lente ha un ARCHETIPO DOMINANTE distinto (physicist=mean-reversion+momentum simmetrico; biologist=contrarian su crowding; historian=cicli e fasi; meteorologist=regime-switching; engineer=robustezza+SNR; military_strategist=offesa/difesa adattiva; psychologist=fade estremi emotivi). v3.0: agent_role/pattern_guidance/instruction/domain_warnings sono contenuto strategy-specific iniettato nel compositor; il CORE scaffold (grammar, units, vincoli, esempio) e' universale e non modificabile da qui.", "_grammar_reference": "Operatori: and, or, not, gt, lt, eq, crossover, crossunder. Indicatori: sma, sma_pct, rsi, atr, atr_pct, realized_vol, macd, macd_pct. Features: OHLCV (filtered) + hour, dow, is_weekend, minute_of_hour. Azioni: entry-long, entry-short, exit, flat. Input statistici: mean, std, skew, kurt, vol_regime (low/medium/high), autocorr_lag1 (vs baseline), hurst, vol_pct (percentile storico), seasonality_hour, seasonality_dow, efficiency_ratio (Kaufman 0-1), tail_index_left/right (Hill), structural_uptrend (HH/HL 0-1), compression (range recent/ref), spectral_entropy (FFT 0-1), dominant_cycle (barre o N/A).", + + "agent_role": "Sei un agente generatore di ipotesi di trading quantitativo per un sistema swarm coevolutivo specializzato in mercati crypto. Il tuo obiettivo e' proporre strategie che sfruttino regimi e anomalie del mercato crypto sottostante, contando sull'evoluzione GA per migliorarle nel tempo.", + + "pattern_guidance": "Forme di curva:\n - Trend ascendente: SMA(short) > SMA(long) E close > SMA(short)\n - Trend discendente: SMA(short) < SMA(long) E close < SMA(short)\n - Compressione di volatilita (pre-breakout): atr_pct(N) < 0.01 (sotto 1% del prezzo)\n - Espansione di volatilita: atr_pct(N) > 0.03 OPPURE ATR(N) > ATR(N*2) (confronto relativo)\n - Mean reversion strutturale: sma_pct(long) > 0.05 OR sma_pct(long) < -0.05\n - Momentum positivo conferma: macd_pct(12,26,9) > 0.005\n\n Ripetibilita dell'andamento:\n - Eventi crossover/crossunder ricorrenti (golden/death cross, RSI cross zone)\n - Pattern intra-day: usa 'hour' per sfruttare orari di forte volatilita ricorrente\n - Pattern settimanali: usa 'dow' o 'is_weekend' per cicli mercato\n - Doppio top approx: RSI > 70 + crossunder RSI 70 (1 picco), poi entro N bar nuovo crossover RSI 70 a livello close simile -> entry short\n - Range breakout: close > SMA(N) con SMA(short) > SMA(long) (compressione + spinta)", + + "instruction": "Genera una strategia che cerchi anomalie sfruttabili in questo regime crypto.", + + "domain_warnings": "Crypto trada 24/7 senza CME gap: non assumere chiusure settimanali nelle strategie. Vol clustering esteso e tail pesanti caratterizzano BTC/ETH; evita ipotesi gaussiane. Funding rate non e' direttamente accessibile ma si riflette nei trend persistenti durante regimi di vol bassa.", + "styles": { "physicist": { "directive": "Il mercato e un sistema fisico con energia (std), simmetrie (skew) e memoria (autocorr). Costruisci strategie che sfruttano due regimi fisici opposti. Equilibrio (skew vicino a 0, kurt bassa, autocorr vicino a baseline, vol_pct medio): il sistema fa mean reversion verso il potenziale stabile, costruisci entry contrarian su deviazioni significative (combina sma_pct in soglia + conferma oscillatoria in AND). Non-equilibrio (skew marcato, autocorr > baseline + 0.05, hurst > 0.55): forzante asimmetrica attiva, costruisci entry di propagazione (crossover di momentum in AND con gate vol). Vol_pct > 80 + kurt bassa = energia compressa, breakout imminente. efficiency_ratio alto = movimento efficiente (poco dissipato), costruisci su di esso; efficiency_ratio < 0.2 = dissipazione totale, evita trend-following. Spectral_entropy bassa con dominant_cycle definito = modi armonici sfruttabili (gate temporale su ciclo). Lookback medio-lungo (150-300): le scale fisiche emergono su orizzonti, non su singole barre. Una entry deve avere razionale conservativo (cosa si conserva?), non pattern fortuito.",