fix(protocol): arity check stretto per indicator + reject nested expressions
Run reale phase1-real-003 ha rivelato: l'LLM genera occasionalmente "(indicator sma 20 50)" o "(indicator sma (feature close) 20)". Il primo crashava _ind_sma con TypeError. Il secondo passava attraverso il validator ma non era supportato dal compiler. Validator ora: - Aggiunge INDICATOR_ARITY: sma/rsi/atr/realized_vol = 1 arg, macd = 0-3. - Rifiuta esplicitamente Node fra gli args di indicator (no-nesting Phase 1). - Rifiuta arity fuori range con messaggio chiaro. Strategie con questi pattern vengono ora rigettate dal validator come parse_error invece di crashare il run. Test suite resta 122 PASSED. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,17 @@ from .parser import Node, Strategy
|
|||||||
KNOWN_INDICATORS: frozenset[str] = frozenset({"sma", "rsi", "atr", "macd", "realized_vol"})
|
KNOWN_INDICATORS: frozenset[str] = frozenset({"sma", "rsi", "atr", "macd", "realized_vol"})
|
||||||
KNOWN_FEATURES: frozenset[str] = frozenset({"open", "high", "low", "close", "volume"})
|
KNOWN_FEATURES: frozenset[str] = frozenset({"open", "high", "low", "close", "volume"})
|
||||||
|
|
||||||
|
# Numero di parametri numerici accettati dopo il nome dell'indicatore.
|
||||||
|
# La tupla (min, max) include solo i numeri (gli argomenti di tipo Node sono
|
||||||
|
# proibiti dal compiler - gli indicatori non sono annidabili in Phase 1).
|
||||||
|
INDICATOR_ARITY: dict[str, tuple[int, int]] = {
|
||||||
|
"sma": (1, 1), # length
|
||||||
|
"rsi": (1, 1), # length
|
||||||
|
"atr": (1, 1), # length
|
||||||
|
"realized_vol": (1, 1), # window
|
||||||
|
"macd": (0, 3), # fast, slow, signal (tutti opzionali con default)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ValidationError(Exception):
|
class ValidationError(Exception):
|
||||||
"""Raised when an AST violates Phase 1 protocol semantics."""
|
"""Raised when an AST violates Phase 1 protocol semantics."""
|
||||||
@@ -55,12 +66,25 @@ def _validate_node(node: Node, _expect_bool: bool) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if node.kind == "indicator":
|
if node.kind == "indicator":
|
||||||
if len(node.args) < 2:
|
if len(node.args) < 1:
|
||||||
raise ValidationError("'indicator' needs >=2 args (name, length)")
|
raise ValidationError("'indicator' needs >=1 args (name [, params...])")
|
||||||
name_node = node.args[0]
|
name_node = node.args[0]
|
||||||
ind_name = name_node.kind if isinstance(name_node, Node) else str(name_node)
|
ind_name = name_node.kind if isinstance(name_node, Node) else str(name_node)
|
||||||
if ind_name not in KNOWN_INDICATORS:
|
if ind_name not in KNOWN_INDICATORS:
|
||||||
raise ValidationError(f"unknown indicator: {ind_name}")
|
raise ValidationError(f"unknown indicator: {ind_name}")
|
||||||
|
# Gli indicatori non accettano Node come params (no-nesting in Phase 1).
|
||||||
|
for a in node.args[1:]:
|
||||||
|
if isinstance(a, Node):
|
||||||
|
raise ValidationError(
|
||||||
|
f"indicator '{ind_name}' does not accept nested expressions; "
|
||||||
|
f"only numeric literals (got node {a.kind})"
|
||||||
|
)
|
||||||
|
n_params = len(node.args) - 1
|
||||||
|
min_p, max_p = INDICATOR_ARITY[ind_name]
|
||||||
|
if not (min_p <= n_params <= max_p):
|
||||||
|
raise ValidationError(
|
||||||
|
f"indicator '{ind_name}' arity {n_params} out of [{min_p},{max_p}]"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if node.kind == "feature":
|
if node.kind == "feature":
|
||||||
|
|||||||
Reference in New Issue
Block a user