feat(protocol): validate arity + semantics of 3 Pythagoras indicators

This commit is contained in:
Adriano Dal Pastro
2026-05-19 13:29:16 +00:00
parent 6a9e2c28b1
commit 2aa5646aeb
2 changed files with 109 additions and 0 deletions
@@ -39,6 +39,10 @@ INDICATOR_ARITY: dict[str, tuple[int, int]] = {
"realized_vol": (1, 1), # window "realized_vol": (1, 1), # window
"macd": (0, 3), # fast, slow, signal (tutti opzionali) "macd": (0, 3), # fast, slow, signal (tutti opzionali)
"macd_pct": (0, 3), # macd/close, frazionale (per confronti con literal) "macd_pct": (0, 3), # macd/close, frazionale (per confronti con literal)
# Pythagoras indicators (params encoded as floats)
"candle_pattern": (4, 13), # [length, sym0, ..., sym_{length-1}]
"pythagorean_ratio": (1, 1), # lookback in [12,200]
"fractal_mirror": (2, 2), # k in [3,12], axis_int in {0=h,1=v}
} }
@@ -110,3 +114,37 @@ def _validate_indicator(node: IndicatorNode) -> None:
raise ValidationError( raise ValidationError(
f"indicator '{node.name}' arity {n_params} out of [{min_p},{max_p}]" f"indicator '{node.name}' arity {n_params} out of [{min_p},{max_p}]"
) )
# Pythagoras-specific param semantics
name = node.name
if name == "candle_pattern":
length = int(node.params[0])
if not (3 <= length <= 12):
raise ValidationError(
f"candle_pattern length must be in [3,12], got {length}"
)
if n_params != 1 + length:
raise ValidationError(
f"candle_pattern: expected 1+length={1 + length} params, got {n_params}"
)
for i, sym in enumerate(node.params[1:], start=1):
sym_int = int(sym)
if sym_int not in (0, 1, 2):
raise ValidationError(
f"candle_pattern sym[{i - 1}] must be 0(U)/1(D)/2(doji), got {sym}"
)
elif name == "pythagorean_ratio":
lookback = int(node.params[0])
if not (12 <= lookback <= 200):
raise ValidationError(
f"pythagorean_ratio lookback in [12,200], got {lookback}"
)
elif name == "fractal_mirror":
k = int(node.params[0])
if not (3 <= k <= 12):
raise ValidationError(f"fractal_mirror k must be in [3,12], got {k}")
axis_int = int(node.params[1])
if axis_int not in (0, 1):
raise ValidationError(
f"fractal_mirror axis must be 0(h)/1(v), got {axis_int}"
)
@@ -0,0 +1,71 @@
"""Validator accetta i 3 nuovi indicatori candle con arity corrette."""
from __future__ import annotations
import json
import pytest
from multi_swarm_core.protocol.parser import parse_strategy
from multi_swarm_core.protocol.validator import ValidationError, validate_strategy
def _strategy_with_indicator(name: str, params: list[float]) -> str:
return json.dumps(
{
"rules": [
{
"condition": {
"op": "gt",
"args": [
{"kind": "indicator", "name": name, "params": params},
{"kind": "literal", "value": 0.5},
],
},
"action": "entry-long",
}
]
}
)
def test_candle_pattern_valid_min_arity() -> None:
s = parse_strategy(_strategy_with_indicator("candle_pattern", [3, 0, 1, 0]))
validate_strategy(s)
def test_candle_pattern_valid_max_arity() -> None:
s = parse_strategy(_strategy_with_indicator("candle_pattern", [12] + [0] * 12))
validate_strategy(s)
def test_candle_pattern_rejects_too_few_params() -> None:
s = parse_strategy(_strategy_with_indicator("candle_pattern", [3, 0, 1]))
with pytest.raises(ValidationError):
validate_strategy(s)
def test_pythagorean_ratio_valid() -> None:
s = parse_strategy(_strategy_with_indicator("pythagorean_ratio", [89]))
validate_strategy(s)
def test_pythagorean_ratio_rejects_zero_params() -> None:
s = parse_strategy(_strategy_with_indicator("pythagorean_ratio", []))
with pytest.raises(ValidationError):
validate_strategy(s)
def test_fractal_mirror_valid_h() -> None:
s = parse_strategy(_strategy_with_indicator("fractal_mirror", [12, 0]))
validate_strategy(s)
def test_fractal_mirror_valid_v() -> None:
s = parse_strategy(_strategy_with_indicator("fractal_mirror", [12, 1]))
validate_strategy(s)
def test_fractal_mirror_rejects_one_param() -> None:
s = parse_strategy(_strategy_with_indicator("fractal_mirror", [12]))
with pytest.raises(ValidationError):
validate_strategy(s)