59 lines
2.5 KiB
Python
59 lines
2.5 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import pytest
|
|
from django.core.management import call_command
|
|
|
|
from control_plane.trading_studio.indicators.definitions import IndicatorVariant, Role
|
|
from control_plane.trading_studio.indicators.feature_catalog import (
|
|
FEATURE_CATALOG_V1,
|
|
FEATURE_CATALOG_V1_PATH,
|
|
FEATURE_SAFETY_AUDIT_V1_PATH,
|
|
CausalClassification,
|
|
FeatureSupportState,
|
|
FeatureUnavailableError,
|
|
feature_catalog_artifact_bytes,
|
|
feature_definition,
|
|
feature_safety_audit_artifact_bytes,
|
|
require_validated_features,
|
|
)
|
|
|
|
|
|
def test_catalog_explicitly_records_validated_coverage_and_source_only_scope():
|
|
assert len(FEATURE_CATALOG_V1) == 1107
|
|
validated = {
|
|
item.key
|
|
for item in FEATURE_CATALOG_V1
|
|
if item.support_state == FeatureSupportState.VALIDATED
|
|
}
|
|
assert len(validated) == 80
|
|
artifact = json.loads(feature_catalog_artifact_bytes())
|
|
assert artifact["implementation_coverage"]["coverage"] == "80 of 1107"
|
|
assert artifact["evaluation"] == "not evaluated via reference runtime; no strategy search"
|
|
|
|
|
|
def test_safety_audit_derives_causal_classification_from_source_semantics():
|
|
trailing = feature_definition(IndicatorVariant(30, 14, 0.0, Role.OSC))
|
|
index_based = feature_definition(IndicatorVariant(117, 1, 0.0, Role.TREND))
|
|
alias = feature_definition(IndicatorVariant(25, 14, 0.0, Role.LEVEL))
|
|
assert trailing.causal_classification == CausalClassification.TRAILING_WINDOW_CAUSAL
|
|
assert index_based.causal_classification == CausalClassification.INDEX_POSITION_CAUSAL
|
|
assert alias.support_state == FeatureSupportState.ALIAS_REFUSED
|
|
policy = json.loads(feature_safety_audit_artifact_bytes())["fail_closed_policy"]
|
|
assert policy.startswith("Any requested")
|
|
|
|
|
|
def test_non_validated_historical_variants_fail_closed_without_baseline_expansion():
|
|
validated = IndicatorVariant(30, 14, 0.0, Role.OSC)
|
|
unvalidated = IndicatorVariant(30, 12, 0.0, Role.OSC)
|
|
with pytest.raises(FeatureUnavailableError, match="fails closed"):
|
|
require_validated_features([validated, unvalidated])
|
|
|
|
|
|
def test_generator_writes_machine_readable_catalog_and_audit(tmp_path):
|
|
call_command("tradingstudio_generate_feature_catalog", "--output-dir", str(tmp_path))
|
|
catalog = tmp_path / FEATURE_CATALOG_V1_PATH.name
|
|
audit = tmp_path / FEATURE_SAFETY_AUDIT_V1_PATH.name
|
|
assert json.loads(catalog.read_text())["catalog"] == "FEATURE_CATALOG_V1"
|
|
assert json.loads(audit.read_text())["audit"] == "FEATURE_SAFETY_AUDIT_V1"
|