refactor(prompt): SYSTEM_TEMPLATE diventa compositor — strategy controlla agent_role/pattern_guidance/instruction
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) <noreply@anthropic.com>
This commit is contained in:
@@ -56,12 +56,9 @@ class HypothesisProposal:
|
|||||||
n_attempts: int = 1
|
n_attempts: int = 1
|
||||||
|
|
||||||
|
|
||||||
SYSTEM_TEMPLATE = """\
|
# === CORE SCAFFOLD constants (universal, legato al protocol/compiler) ===
|
||||||
Sei un agente generatore di ipotesi di trading quantitativo per un sistema swarm.
|
|
||||||
|
|
||||||
Il tuo stile cognitivo: {cognitive_style}
|
|
||||||
Direttiva personale: {system_prompt}
|
|
||||||
|
|
||||||
|
_SYSTEM_GRAMMAR_SPEC = """\
|
||||||
Devi proporre una strategia di trading espressa in JSON STRETTO.
|
Devi proporre una strategia di trading espressa in JSON STRETTO.
|
||||||
La risposta deve essere un singolo oggetto JSON dentro fence ```json...```
|
La risposta deve essere un singolo oggetto JSON dentro fence ```json...```
|
||||||
con questa shape:
|
con questa shape:
|
||||||
@@ -136,35 +133,18 @@ Esempi di gating temporale:
|
|||||||
{{"op": "eq", "args": [{{"kind": "feature", "name": "is_weekend"}}, {{"kind": "literal", "value": 1}}]}}
|
{{"op": "eq", "args": [{{"kind": "feature", "name": "is_weekend"}}, {{"kind": "literal", "value": 1}}]}}
|
||||||
|
|
||||||
Leaf - letterale numerico:
|
Leaf - letterale numerico:
|
||||||
{{"kind": "literal", "value": 70.0}}
|
{{"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.
|
|
||||||
|
|
||||||
|
_SYSTEM_CONSTRAINTS = """\
|
||||||
VINCOLI
|
VINCOLI
|
||||||
- Gli indicator NON sono annidabili: 'params' accetta solo numeri, mai altri nodi.
|
- 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.
|
- Le regole sono valutate in ordine; la prima che matcha vince per ogni timestamp.
|
||||||
- Default action se nessuna regola matcha = flat.
|
- Default action se nessuna regola matcha = flat.
|
||||||
- 'op' e 'kind' sono mutuamente esclusivi sullo stesso nodo.
|
- '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:
|
Esempio:
|
||||||
|
|
||||||
```json
|
```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 = """\
|
USER_TEMPLATE = """\
|
||||||
@@ -213,7 +225,7 @@ Geometria & frattali:
|
|||||||
Feature accessibili dal tuo genoma: {feature_access}.
|
Feature accessibili dal tuo genoma: {feature_access}.
|
||||||
Lookback massimo che puoi usare nel ragionamento: {lookback_window} barre.
|
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,
|
genome: HypothesisAgentGenome,
|
||||||
market: MarketSummary,
|
market: MarketSummary,
|
||||||
) -> HypothesisProposal:
|
) -> HypothesisProposal:
|
||||||
system = SYSTEM_TEMPLATE.format(
|
system = _build_system_prompt(self._prompt_library, genome)
|
||||||
cognitive_style=genome.cognitive_style,
|
|
||||||
system_prompt=genome.system_prompt,
|
|
||||||
)
|
|
||||||
dominant_cycle_str = (
|
dominant_cycle_str = (
|
||||||
f"{market.dominant_cycle:.0f} barre"
|
f"{market.dominant_cycle:.0f} barre"
|
||||||
if market.dominant_cycle is not None
|
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_keys = self._prompt_library.focus_metrics_for(genome.cognitive_style)
|
||||||
focus_block = _render_focus_block(focus_keys, market) if focus_keys else ""
|
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(
|
original_user = USER_TEMPLATE.format(
|
||||||
symbol=market.symbol,
|
symbol=market.symbol,
|
||||||
timeframe=market.timeframe,
|
timeframe=market.timeframe,
|
||||||
@@ -396,6 +409,7 @@ class HypothesisAgent:
|
|||||||
dominant_cycle_str=dominant_cycle_str,
|
dominant_cycle_str=dominant_cycle_str,
|
||||||
feature_access=", ".join(genome.feature_access),
|
feature_access=", ".join(genome.feature_access),
|
||||||
lookback_window=genome.lookback_window,
|
lookback_window=genome.lookback_window,
|
||||||
|
instruction=instruction,
|
||||||
) + focus_block
|
) + focus_block
|
||||||
|
|
||||||
completions: list[CompletionResult] = []
|
completions: list[CompletionResult] = []
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ Schema JSON atteso::
|
|||||||
"styles": {
|
"styles": {
|
||||||
"<name>": {"directive": "<testo system_prompt>"},
|
"<name>": {"directive": "<testo system_prompt>"},
|
||||||
...
|
...
|
||||||
}
|
},
|
||||||
|
"agent_role": "<descrizione agente, opzionale>",
|
||||||
|
"pattern_guidance": "<sezione PATTERN GUIDANCE, opzionale>",
|
||||||
|
"instruction": "<frase finale USER, opzionale>",
|
||||||
|
"domain_warnings": "<warning di dominio, opzionale>"
|
||||||
}
|
}
|
||||||
|
|
||||||
I 6 stili default (physicist, biologist, historian, meteorologist,
|
I 6 stili default (physicist, biologist, historian, meteorologist,
|
||||||
@@ -26,7 +30,7 @@ per backcompat con test/script senza file esterno.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
_DEFAULT_STYLES: dict[str, str] = {
|
_DEFAULT_STYLES: dict[str, str] = {
|
||||||
@@ -61,12 +65,39 @@ class PromptLibraryError(ValueError):
|
|||||||
"""Sollevata su JSON malformato o stili invalid."""
|
"""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)
|
@dataclass(frozen=True)
|
||||||
class PromptLibrary:
|
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]
|
styles: dict[str, str]
|
||||||
focus: dict[str, list[str]] # style -> lista metriche prioritarie
|
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:
|
def __post_init__(self) -> None:
|
||||||
if not self.styles:
|
if not self.styles:
|
||||||
@@ -78,15 +109,48 @@ class PromptLibrary:
|
|||||||
raise PromptLibraryError(
|
raise PromptLibraryError(
|
||||||
f"directive vuota o invalida per stile {name!r}"
|
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
|
@classmethod
|
||||||
def default(cls) -> PromptLibrary:
|
def default(cls) -> PromptLibrary:
|
||||||
"""Libreria builtin con i 6 stili originali (fallback senza file)."""
|
"""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
|
@classmethod
|
||||||
def from_json(cls, path: Path | str) -> PromptLibrary:
|
def from_json(cls, path: Path | str) -> PromptLibrary:
|
||||||
"""Carica da file JSON con schema ``{"styles": {<name>: {"directive": "...", "focus_metrics": [...]}}}``."""
|
"""Carica da file JSON.
|
||||||
|
|
||||||
|
Schema::
|
||||||
|
|
||||||
|
{
|
||||||
|
"styles": {<name>: {"directive": "...", "focus_metrics": [...]}},
|
||||||
|
"agent_role": "...", // opzionale
|
||||||
|
"pattern_guidance": "...", // opzionale
|
||||||
|
"instruction": "...", // opzionale
|
||||||
|
"domain_warnings": "..." // opzionale
|
||||||
|
}
|
||||||
|
"""
|
||||||
p = Path(path)
|
p = Path(path)
|
||||||
try:
|
try:
|
||||||
data = json.loads(p.read_text(encoding="utf-8"))
|
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)}"
|
f"focus_metrics di {name!r} deve essere lista, non {type(fm)}"
|
||||||
)
|
)
|
||||||
focus[name] = [str(x) for x in 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
|
@property
|
||||||
def cognitive_styles(self) -> tuple[str, ...]:
|
def cognitive_styles(self) -> tuple[str, ...]:
|
||||||
|
|||||||
@@ -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
|
call_kwargs = fake_llm.complete.call_args.kwargs
|
||||||
user_msg = call_kwargs["user"]
|
user_msg = call_kwargs["user"]
|
||||||
assert "Focus per la tua lente:" not in user_msg
|
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
|
||||||
|
|||||||
@@ -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 == ""
|
||||||
@@ -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.",
|
"_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": "2.2",
|
"_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. 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.",
|
"_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).",
|
"_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).",
|
"_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": {
|
"styles": {
|
||||||
"physicist": {
|
"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.",
|
"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.",
|
||||||
|
|||||||
Reference in New Issue
Block a user