feat(prompt_library): v3.2 custom_indicators_spec — fix LLM parse rate on Pythagoras indicators
This commit is contained in:
@@ -181,6 +181,12 @@ def _build_system_prompt(lib: PromptLibrary, genome: HypothesisAgentGenome) -> s
|
|||||||
parts.append("")
|
parts.append("")
|
||||||
# 3. Grammar spec (core scaffold)
|
# 3. Grammar spec (core scaffold)
|
||||||
parts.append(_SYSTEM_GRAMMAR_SPEC)
|
parts.append(_SYSTEM_GRAMMAR_SPEC)
|
||||||
|
# 3b. Custom indicators spec (da prompts.json, opzionale) - estende la lista
|
||||||
|
# "Leaf - indicatori" con firme strategy-specific non note al core.
|
||||||
|
if lib.custom_indicators_spec:
|
||||||
|
parts.append("\nINDICATORI CUSTOM (firme aggiuntive, applica le stesse regole grammaticali):\n")
|
||||||
|
parts.append(lib.custom_indicators_spec)
|
||||||
|
parts.append("")
|
||||||
# 4. Pattern guidance (da prompts.json, opzionale)
|
# 4. Pattern guidance (da prompts.json, opzionale)
|
||||||
if lib.pattern_guidance:
|
if lib.pattern_guidance:
|
||||||
parts.append(
|
parts.append(
|
||||||
|
|||||||
@@ -101,6 +101,10 @@ class PromptLibrary:
|
|||||||
domain_warnings: str = field(default="") # opzionale: warning di dominio (es. crypto 24/7)
|
domain_warnings: str = field(default="") # opzionale: warning di dominio (es. crypto 24/7)
|
||||||
anti_patterns: str = field(default="") # NEW v3.1: lista esplicita di pattern da evitare
|
anti_patterns: str = field(default="") # NEW v3.1: lista esplicita di pattern da evitare
|
||||||
output_priorities: str = field(default="") # NEW v3.1: trade-off espliciti di output
|
output_priorities: str = field(default="") # NEW v3.1: trade-off espliciti di output
|
||||||
|
# NEW v3.2: firme formali (arity + range) di indicatori strategy-specific
|
||||||
|
# non noti al core. Iniettato in _SYSTEM_GRAMMAR_SPEC come estensione della
|
||||||
|
# lista "Leaf - indicatori" per consentire alla LLM di chiamarli correttamente.
|
||||||
|
custom_indicators_spec: str = field(default="")
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
if not self.styles:
|
if not self.styles:
|
||||||
@@ -120,6 +124,7 @@ class PromptLibrary:
|
|||||||
("domain_warnings", self.domain_warnings),
|
("domain_warnings", self.domain_warnings),
|
||||||
("anti_patterns", self.anti_patterns),
|
("anti_patterns", self.anti_patterns),
|
||||||
("output_priorities", self.output_priorities),
|
("output_priorities", self.output_priorities),
|
||||||
|
("custom_indicators_spec", self.custom_indicators_spec),
|
||||||
):
|
):
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
raise PromptLibraryError(
|
raise PromptLibraryError(
|
||||||
@@ -142,6 +147,7 @@ class PromptLibrary:
|
|||||||
domain_warnings="",
|
domain_warnings="",
|
||||||
anti_patterns="",
|
anti_patterns="",
|
||||||
output_priorities="",
|
output_priorities="",
|
||||||
|
custom_indicators_spec="",
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -204,6 +210,12 @@ class PromptLibrary:
|
|||||||
raise PromptLibraryError(f"anti_patterns deve essere stringa, non {type(anti_patterns_raw)}")
|
raise PromptLibraryError(f"anti_patterns deve essere stringa, non {type(anti_patterns_raw)}")
|
||||||
if not isinstance(output_priorities_raw, str):
|
if not isinstance(output_priorities_raw, str):
|
||||||
raise PromptLibraryError(f"output_priorities deve essere stringa, non {type(output_priorities_raw)}")
|
raise PromptLibraryError(f"output_priorities deve essere stringa, non {type(output_priorities_raw)}")
|
||||||
|
# Parse new optional top-level field (v3.2)
|
||||||
|
custom_indicators_spec_raw = data.get("custom_indicators_spec", "")
|
||||||
|
if not isinstance(custom_indicators_spec_raw, str):
|
||||||
|
raise PromptLibraryError(
|
||||||
|
f"custom_indicators_spec deve essere stringa, non {type(custom_indicators_spec_raw)}"
|
||||||
|
)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
styles=styles,
|
styles=styles,
|
||||||
@@ -214,6 +226,7 @@ class PromptLibrary:
|
|||||||
domain_warnings=domain_warnings,
|
domain_warnings=domain_warnings,
|
||||||
anti_patterns=anti_patterns_raw,
|
anti_patterns=anti_patterns_raw,
|
||||||
output_priorities=output_priorities_raw,
|
output_priorities=output_priorities_raw,
|
||||||
|
custom_indicators_spec=custom_indicators_spec_raw,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -420,3 +420,48 @@ def test_build_system_prompt_skips_anti_patterns_and_priorities_when_empty(mocke
|
|||||||
system_msg = call_kwargs["system"]
|
system_msg = call_kwargs["system"]
|
||||||
assert "ANTI-PATTERN" not in system_msg
|
assert "ANTI-PATTERN" not in system_msg
|
||||||
assert "PRIORITA' DI OUTPUT" not in system_msg
|
assert "PRIORITA' DI OUTPUT" not in system_msg
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_system_prompt_includes_custom_indicators_spec(mocker): # type: ignore[no-untyped-def]
|
||||||
|
"""custom_indicators_spec da PromptLibrary appare nel SYSTEM prompt con header."""
|
||||||
|
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={},
|
||||||
|
custom_indicators_spec="CUSTOM_IND_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 "INDICATORI CUSTOM" in system_msg
|
||||||
|
assert "CUSTOM_IND_X" in system_msg
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_system_prompt_skips_custom_indicators_spec_when_empty(mocker): # type: ignore[no-untyped-def]
|
||||||
|
"""custom_indicators_spec='' -> sezione assente 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={},
|
||||||
|
custom_indicators_spec="",
|
||||||
|
)
|
||||||
|
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 "INDICATORI CUSTOM" not in system_msg
|
||||||
|
|||||||
@@ -90,6 +90,28 @@ def test_from_json_loads_anti_patterns_and_output_priorities(tmp_path: Path) ->
|
|||||||
assert lib.output_priorities == "Robustezza > ottimalita."
|
assert lib.output_priorities == "Robustezza > ottimalita."
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_json_loads_custom_indicators_spec(tmp_path: Path) -> None:
|
||||||
|
"""from_json() legge custom_indicators_spec (v3.2): firme di indicatori strategy-specific."""
|
||||||
|
data = {
|
||||||
|
"styles": {"physicist": {"directive": "Cerca leggi conservative."}},
|
||||||
|
"custom_indicators_spec": (
|
||||||
|
"candle_pattern: params=[length, sym0, sym1, ...], length in [3,12], "
|
||||||
|
"sym in {0=U,1=D,2=doji}\n"
|
||||||
|
"pythagorean_ratio: params=[lookback], lookback in [12,200]"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
lib = PromptLibrary.from_json(_write_json(data, tmp_path))
|
||||||
|
assert "candle_pattern" in lib.custom_indicators_spec
|
||||||
|
assert "lookback in [12,200]" in lib.custom_indicators_spec
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_json_defaults_custom_indicators_spec_when_absent(tmp_path: Path) -> None:
|
||||||
|
"""custom_indicators_spec assente -> default stringa vuota."""
|
||||||
|
data = {"styles": {"physicist": {"directive": "Cerca leggi conservative."}}}
|
||||||
|
lib = PromptLibrary.from_json(_write_json(data, tmp_path))
|
||||||
|
assert lib.custom_indicators_spec == ""
|
||||||
|
|
||||||
|
|
||||||
def test_strategy_crypto_directives_ascii_safe() -> None:
|
def test_strategy_crypto_directives_ascii_safe() -> None:
|
||||||
"""REGRESSION GUARD: nessuna directive contiene caratteri > U+007F.
|
"""REGRESSION GUARD: nessuna directive contiene caratteri > U+007F.
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
"_changelog": "v1.0 - Initial release. Schema clonato da strategy_crypto v3.2 con contenuto Pythagoras-aligned.",
|
"_changelog": "v1.0 - Initial release. Schema clonato da strategy_crypto v3.2 con contenuto Pythagoras-aligned.",
|
||||||
"_focus_metrics_design": "Le focus_metrics sono ENFASI per la lente, non filtri. 4 per stile. Devono includere almeno 1 dei 3 indicatori Pythagoras (candle_pattern, pythagorean_ratio, fractal_mirror).",
|
"_focus_metrics_design": "Le focus_metrics sono ENFASI per la lente, non filtri. 4 per stile. Devono includere almeno 1 dei 3 indicatori Pythagoras (candle_pattern, pythagorean_ratio, fractal_mirror).",
|
||||||
"_design_invariants": "(1) ASCII-safe; (2) Archetipo dominante: <metafora> come ancora; (3) Lookback range esplicito; (4) Prima frase 'Il mercato e ...'; (5) Lunghezza directive 800-950 char.",
|
"_design_invariants": "(1) ASCII-safe; (2) Archetipo dominante: <metafora> come ancora; (3) Lookback range esplicito; (4) Prima frase 'Il mercato e ...'; (5) Lunghezza directive 800-950 char.",
|
||||||
"_param_encoding_note": "Vincolo grammar: tutti i params sono float. candle_pattern: [length, sym0, sym1, ...] (sym: 0=U up close>open, 1=D down close<open, 2=doji). pythagorean_ratio: [lookback]. fractal_mirror: [k, axis_int] (0=h temporale, 1=v prezzo).",
|
"_param_encoding_note": "Promosso a campo top-level 'custom_indicators_spec' (v3.2). Vedi sotto.",
|
||||||
|
|
||||||
|
"custom_indicators_spec": "Indicatori Pythagoras (oltre a sma/sma_pct/rsi/atr/atr_pct/realized_vol/macd/macd_pct gia documentati sopra). REGOLA CRITICA: params accetta SOLO numeri float, MAI stringhe e MAI altri nodi.\n\n {\"kind\": \"indicator\", \"name\": \"candle_pattern\", \"params\": [length, sym0, sym1, ..., sym_{length-1}]}\n length: int in [3,12] (numero di candele consecutive che devono matchare)\n sym_i: int in {0, 1, 2} (0=U up close>open, 1=D down close<open, 2=doji)\n Esempio: pattern U-D-U di 3 candele = params=[3, 0, 1, 0]\n Esempio: pattern D-D-D-U (reversal classico) = params=[4, 1, 1, 1, 0]\n Output: 1.0 se le ultime length candele matchano, 0.0 altrimenti. Confronta solo con literal 0.0 o 1.0.\n\n {\"kind\": \"indicator\", \"name\": \"pythagorean_ratio\", \"params\": [lookback]}\n lookback: int in [12,200] (finestra rolling per max/min close)\n Esempio: ratio su finestra 89 (Fibonacci) = params=[89]\n Output: max(close[-lookback:]) / min(close[-lookback:]), adimensionale >= 1.0.\n Confronta con literal vicini a phi=1.618, 1/phi=0.618 (in realta usa 1.618 perche ratio >= 1), sqrt2=1.414, pi/2=1.571, e=2.718.\n\n {\"kind\": \"indicator\", \"name\": \"fractal_mirror\", \"params\": [k, axis_int]}\n k: int in [3,12] (lunghezza finestra di confronto)\n axis_int: int in {0, 1} (0=h mirror temporale, 1=v mirror prezzo)\n Esempio: mirror temporale su 8 candele = params=[8, 0]\n Esempio: mirror prezzo su 6 candele = params=[6, 1]\n Output: correlation di Pearson in [-1.0, 1.0] tra finestra e suo mirror. Confronta con literal in (-1, 1), tipicamente |0.5|-|0.8|.\n\nESEMPI di confronti CORRETTI:\n candle_pattern di 3 candele U-D-U attiva: {\"op\": \"eq\", \"args\": [{\"kind\":\"indicator\",\"name\":\"candle_pattern\",\"params\":[3,0,1,0]}, {\"kind\":\"literal\",\"value\":1.0}]}\n ratio phi entro 0.5%: {\"op\": \"and\", \"args\": [{\"op\":\"gt\",\"args\":[{\"kind\":\"indicator\",\"name\":\"pythagorean_ratio\",\"params\":[89]},{\"kind\":\"literal\",\"value\":1.610}]}, {\"op\":\"lt\",\"args\":[{\"kind\":\"indicator\",\"name\":\"pythagorean_ratio\",\"params\":[89]},{\"kind\":\"literal\",\"value\":1.626}]}]}\n mirror temporale forte: {\"op\": \"gt\", \"args\": [{\"kind\":\"indicator\",\"name\":\"fractal_mirror\",\"params\":[8,0]}, {\"kind\":\"literal\",\"value\":0.7}]}\n\nESEMPI ERRATI (dead branch o validation error):\n candle_pattern con stringa: params=[\"UDU\"] (errato: params devono essere numeri)\n candle_pattern: params=[3, \"U\", \"D\", \"U\"] (errato: usa codici 0/1/2)\n pythagorean_ratio: params=[\"close\"] (errato: params devono essere numeri, non nomi feature)\n pythagorean_ratio: params=[5] (errato: lookback < 12)\n pythagorean_ratio: params=[89, 1.618] (errato: arity 1, non 2)\n fractal_mirror: params=[0, 1] (errato: k < 3)\n fractal_mirror: params=[100, 0] (errato: k > 12)\n fractal_mirror: params=[8] (errato: arity 2, manca axis_int)",
|
||||||
|
|
||||||
"agent_role": "Sei un agente generatore di ipotesi di trading quantitativo per un sistema swarm coevolutivo che cerca pattern frattali ricorrenti sui mercati crypto secondo il framework Pythagoras-Malanga (frattali H-C, trasformata di Fourier, geometria Evideon). Sei parte di una popolazione che esplora collettivamente lo spazio dei pattern: la diversita delle ipotesi e un asset critico. Preferisci esplorare territori meno ovvi rispetto a quelli che la tua lente cognitiva renderebbe predicibili. La strategia che produci deve essere riconoscibile come emanata dal tuo stile.",
|
"agent_role": "Sei un agente generatore di ipotesi di trading quantitativo per un sistema swarm coevolutivo che cerca pattern frattali ricorrenti sui mercati crypto secondo il framework Pythagoras-Malanga (frattali H-C, trasformata di Fourier, geometria Evideon). Sei parte di una popolazione che esplora collettivamente lo spazio dei pattern: la diversita delle ipotesi e un asset critico. Preferisci esplorare territori meno ovvi rispetto a quelli che la tua lente cognitiva renderebbe predicibili. La strategia che produci deve essere riconoscibile come emanata dal tuo stile.",
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user