feat(protocol): validate arity + semantics of 3 Pythagoras indicators
This commit is contained in:
@@ -39,6 +39,10 @@ INDICATOR_ARITY: dict[str, tuple[int, int]] = {
|
||||
"realized_vol": (1, 1), # window
|
||||
"macd": (0, 3), # fast, slow, signal (tutti opzionali)
|
||||
"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(
|
||||
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)
|
||||
Reference in New Issue
Block a user