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
|
"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)
|
||||||
Reference in New Issue
Block a user