Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cb794674c |
@@ -3271,143 +3271,6 @@ def evaluate_phase3_formula(name: str, **inputs: pd.Series) -> pd.Series:
|
||||
return function(*(inputs[field] for field in required_inputs))
|
||||
|
||||
|
||||
# ── Phase 4 formula contract: frozen alpha051-alpha100 surface ──────────────
|
||||
|
||||
# Phase 4 extends the versioned formula contract without mutating the Phase 3
|
||||
# catalogue, digest, dispatch surface, or the existing formula functions.
|
||||
ALPHA158_PHASE4_FORMULA_CONTRACT_VERSION = "1.0.0"
|
||||
ALPHA158_PHASE4_FORMULA_CATALOG_SHA256 = (
|
||||
"858daf5e2abab5063fc28fcf7c79936096e2c76dde582fc7bab78b3458f17054"
|
||||
)
|
||||
|
||||
_PHASE4_FORMULA_FUNCTIONS: dict[str, Callable[..., pd.Series]] = {
|
||||
"alpha_051": alpha_051,
|
||||
"alpha_052": alpha_052,
|
||||
"alpha_053": alpha_053,
|
||||
"alpha_054": alpha_054,
|
||||
"alpha_055": alpha_055,
|
||||
"alpha_056": alpha_056,
|
||||
"alpha_057": alpha_057,
|
||||
"alpha_058": alpha_058,
|
||||
"alpha_059": alpha_059,
|
||||
"alpha_060": alpha_060,
|
||||
"alpha_061": alpha_061,
|
||||
"alpha_062": alpha_062,
|
||||
"alpha_063": alpha_063,
|
||||
"alpha_064": alpha_064,
|
||||
"alpha_065": alpha_065,
|
||||
"alpha_066": alpha_066,
|
||||
"alpha_067": alpha_067,
|
||||
"alpha_068": alpha_068,
|
||||
"alpha_069": alpha_069,
|
||||
"alpha_070": alpha_070,
|
||||
"alpha_071": alpha_071,
|
||||
"alpha_072": alpha_072,
|
||||
"alpha_073": alpha_073,
|
||||
"alpha_074": alpha_074,
|
||||
"alpha_075": alpha_075,
|
||||
"alpha_076": alpha_076,
|
||||
"alpha_077": alpha_077,
|
||||
"alpha_078": alpha_078,
|
||||
"alpha_079": alpha_079,
|
||||
"alpha_080": alpha_080,
|
||||
"alpha_081": alpha_081,
|
||||
"alpha_082": alpha_082,
|
||||
"alpha_083": alpha_083,
|
||||
"alpha_084": alpha_084,
|
||||
"alpha_085": alpha_085,
|
||||
"alpha_086": alpha_086,
|
||||
"alpha_087": alpha_087,
|
||||
"alpha_088": alpha_088,
|
||||
"alpha_089": alpha_089,
|
||||
"alpha_090": alpha_090,
|
||||
"alpha_091": alpha_091,
|
||||
"alpha_092": alpha_092,
|
||||
"alpha_093": alpha_093,
|
||||
"alpha_094": alpha_094,
|
||||
"alpha_095": alpha_095,
|
||||
"alpha_096": alpha_096,
|
||||
"alpha_097": alpha_097,
|
||||
"alpha_098": alpha_098,
|
||||
"alpha_099": alpha_099,
|
||||
"alpha_100": alpha_100,
|
||||
}
|
||||
|
||||
_PHASE4_INPUT_CATEGORIES = {
|
||||
1: "single",
|
||||
2: "pair",
|
||||
3: "triple",
|
||||
4: "quadruple",
|
||||
5: "quintuple",
|
||||
}
|
||||
|
||||
|
||||
def _build_phase4_formula_specs() -> dict[str, dict[str, Any]]:
|
||||
specs: dict[str, dict[str, Any]] = {}
|
||||
for alpha_id, function in _PHASE4_FORMULA_FUNCTIONS.items():
|
||||
meta = ALPHA158_REGISTRY[alpha_id]
|
||||
call_inputs = _phase3_call_inputs(function)
|
||||
formula_inputs = _phase3_string_list(meta, "inputs", alpha_id)
|
||||
input_category = _PHASE4_INPUT_CATEGORIES.get(len(call_inputs))
|
||||
if input_category is None:
|
||||
raise RuntimeError(f"unsupported formula input count for {alpha_id}")
|
||||
specs[alpha_id] = {
|
||||
"name": alpha_id,
|
||||
"contract_version": ALPHA158_PHASE4_FORMULA_CONTRACT_VERSION,
|
||||
"formula": meta["formula"],
|
||||
"category": meta["category"],
|
||||
"complexity": meta["complexity"],
|
||||
"parameters": _phase3_string_list(meta, "params", alpha_id),
|
||||
"description": meta["description"],
|
||||
"references": _phase3_string_list(meta, "references", alpha_id),
|
||||
"call_inputs": list(call_inputs),
|
||||
"formula_inputs": formula_inputs,
|
||||
"input_category": input_category,
|
||||
}
|
||||
return specs
|
||||
|
||||
|
||||
ALPHA158_PHASE4_FORMULA_SPECS: Mapping[str, Mapping[str, Any]] = (
|
||||
_freeze_phase3_formula_specs(_build_phase4_formula_specs())
|
||||
)
|
||||
|
||||
|
||||
def list_phase4_formulas() -> tuple[str, ...]:
|
||||
"""Return the frozen alpha051-alpha100 formula IDs in stable order."""
|
||||
return tuple(ALPHA158_PHASE4_FORMULA_SPECS)
|
||||
|
||||
|
||||
def evaluate_phase4_formula(name: str, **inputs: pd.Series) -> pd.Series:
|
||||
"""Evaluate a Phase 4 formula with an exact, alignment-safe input contract."""
|
||||
if name not in ALPHA158_PHASE4_FORMULA_SPECS:
|
||||
raise KeyError(f"formula {name!r} not registered")
|
||||
|
||||
spec = ALPHA158_PHASE4_FORMULA_SPECS[name]
|
||||
required_inputs = cast(tuple[str, ...], spec["call_inputs"])
|
||||
missing_inputs = [field for field in required_inputs if field not in inputs]
|
||||
unexpected_inputs = sorted(field for field in inputs if field not in required_inputs)
|
||||
if missing_inputs or unexpected_inputs:
|
||||
details: list[str] = []
|
||||
if missing_inputs:
|
||||
details.append(f"missing inputs {missing_inputs}")
|
||||
if unexpected_inputs:
|
||||
details.append(f"unexpected inputs {unexpected_inputs}")
|
||||
raise ValueError(f"invalid inputs for {name}: {'; '.join(details)}")
|
||||
|
||||
for field in required_inputs:
|
||||
if not isinstance(inputs[field], pd.Series):
|
||||
raise TypeError(f"{field} must be a pandas Series")
|
||||
|
||||
primary_field = required_inputs[0]
|
||||
primary = inputs[primary_field]
|
||||
for field in required_inputs[1:]:
|
||||
if not primary.index.equals(inputs[field].index):
|
||||
raise ValueError(f"{field} index must align with {primary_field}")
|
||||
|
||||
function = _PHASE4_FORMULA_FUNCTIONS[name]
|
||||
return function(*(inputs[field] for field in required_inputs))
|
||||
|
||||
|
||||
__all__ = [
|
||||
"rank",
|
||||
"delta",
|
||||
@@ -3446,11 +3309,6 @@ __all__ = [
|
||||
"ALPHA158_PHASE3_FORMULA_SPECS",
|
||||
"list_phase3_formulas",
|
||||
"evaluate_phase3_formula",
|
||||
"ALPHA158_PHASE4_FORMULA_CONTRACT_VERSION",
|
||||
"ALPHA158_PHASE4_FORMULA_CATALOG_SHA256",
|
||||
"ALPHA158_PHASE4_FORMULA_SPECS",
|
||||
"list_phase4_formulas",
|
||||
"evaluate_phase4_formula",
|
||||
"alpha_001",
|
||||
"alpha_002",
|
||||
"alpha_003",
|
||||
|
||||
@@ -14,9 +14,6 @@ from quant_engine.alpha_factors import (
|
||||
ALPHA158_PHASE3_FORMULA_CATALOG_SHA256,
|
||||
ALPHA158_PHASE3_FORMULA_CONTRACT_VERSION,
|
||||
ALPHA158_PHASE3_FORMULA_SPECS,
|
||||
ALPHA158_PHASE4_FORMULA_CATALOG_SHA256,
|
||||
ALPHA158_PHASE4_FORMULA_CONTRACT_VERSION,
|
||||
ALPHA158_PHASE4_FORMULA_SPECS,
|
||||
alpha_001,
|
||||
alpha_002,
|
||||
alpha_003,
|
||||
@@ -178,11 +175,9 @@ from quant_engine.alpha_factors import (
|
||||
evaluate_phase1_operator,
|
||||
evaluate_phase2_operator,
|
||||
evaluate_phase3_formula,
|
||||
evaluate_phase4_formula,
|
||||
list_phase1_operators,
|
||||
list_phase2_operators,
|
||||
list_phase3_formulas,
|
||||
list_phase4_formulas,
|
||||
correlation,
|
||||
covariance,
|
||||
decay_linear,
|
||||
@@ -1646,146 +1641,3 @@ def test_phase3_dispatch_rejects_implicit_series_alignment():
|
||||
close=inputs["close"],
|
||||
volume=misaligned_volume,
|
||||
)
|
||||
|
||||
|
||||
# ── Alpha158 Phase 4: versioned alpha051-alpha100 formula contract ──────────
|
||||
|
||||
|
||||
def test_phase4_formula_catalog_is_versioned_exact_and_content_addressed():
|
||||
import hashlib
|
||||
import json
|
||||
from collections import Counter
|
||||
|
||||
expected_ids = tuple(f"alpha_{number:03d}" for number in range(51, 101))
|
||||
expected_fields = {
|
||||
"name",
|
||||
"contract_version",
|
||||
"formula",
|
||||
"category",
|
||||
"complexity",
|
||||
"parameters",
|
||||
"description",
|
||||
"references",
|
||||
"call_inputs",
|
||||
"formula_inputs",
|
||||
"input_category",
|
||||
}
|
||||
|
||||
assert ALPHA158_PHASE4_FORMULA_CONTRACT_VERSION == "1.0.0"
|
||||
assert list_phase4_formulas() == expected_ids
|
||||
assert tuple(ALPHA158_PHASE4_FORMULA_SPECS) == expected_ids
|
||||
assert Counter(
|
||||
spec["input_category"] for spec in ALPHA158_PHASE4_FORMULA_SPECS.values()
|
||||
) == {"single": 1, "pair": 27, "triple": 11, "quadruple": 10, "quintuple": 1}
|
||||
|
||||
for alpha_id, spec in ALPHA158_PHASE4_FORMULA_SPECS.items():
|
||||
assert set(spec) == expected_fields
|
||||
assert spec["name"] == alpha_id
|
||||
assert spec["contract_version"] == ALPHA158_PHASE4_FORMULA_CONTRACT_VERSION
|
||||
assert spec["formula"] == ALPHA158_REGISTRY[alpha_id]["formula"]
|
||||
|
||||
serializable_specs = {
|
||||
alpha_id: {
|
||||
field: list(value) if isinstance(value, tuple) else value
|
||||
for field, value in spec.items()
|
||||
}
|
||||
for alpha_id, spec in ALPHA158_PHASE4_FORMULA_SPECS.items()
|
||||
}
|
||||
encoded = json.dumps(
|
||||
serializable_specs,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
ensure_ascii=False,
|
||||
).encode()
|
||||
assert hashlib.sha256(encoded).hexdigest() == ALPHA158_PHASE4_FORMULA_CATALOG_SHA256
|
||||
assert ALPHA158_PHASE4_FORMULA_CATALOG_SHA256 == (
|
||||
"858daf5e2abab5063fc28fcf7c79936096e2c76dde582fc7bab78b3458f17054"
|
||||
)
|
||||
|
||||
|
||||
def test_phase4_formula_catalog_is_recursively_immutable():
|
||||
import operator
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
operator.setitem(ALPHA158_PHASE4_FORMULA_SPECS, "alpha_051", {})
|
||||
with pytest.raises(TypeError):
|
||||
operator.setitem(
|
||||
ALPHA158_PHASE4_FORMULA_SPECS["alpha_051"],
|
||||
"formula",
|
||||
"changed",
|
||||
)
|
||||
with pytest.raises(TypeError):
|
||||
operator.setitem(
|
||||
ALPHA158_PHASE4_FORMULA_SPECS["alpha_051"]["call_inputs"],
|
||||
0,
|
||||
"volume",
|
||||
)
|
||||
|
||||
|
||||
def test_phase4_catalog_freezes_callable_and_formula_inputs():
|
||||
import inspect
|
||||
|
||||
for alpha_id, spec in ALPHA158_PHASE4_FORMULA_SPECS.items():
|
||||
function = getattr(alpha_factors_module, alpha_id)
|
||||
signature_inputs = tuple(
|
||||
"open" if name == "open_" else name
|
||||
for name in inspect.signature(function).parameters
|
||||
)
|
||||
assert spec["call_inputs"] == signature_inputs
|
||||
assert spec["formula_inputs"] == tuple(ALPHA158_REGISTRY[alpha_id]["inputs"])
|
||||
|
||||
assert ALPHA158_PHASE4_FORMULA_SPECS["alpha_055"]["call_inputs"] == (
|
||||
"open",
|
||||
"high",
|
||||
"low",
|
||||
"volume",
|
||||
"close",
|
||||
)
|
||||
|
||||
|
||||
def test_phase4_dispatch_matches_all_existing_alpha051_alpha100_functions():
|
||||
inputs = _phase3_market_inputs()
|
||||
|
||||
for alpha_id, spec in ALPHA158_PHASE4_FORMULA_SPECS.items():
|
||||
call_inputs = spec["call_inputs"]
|
||||
function = getattr(alpha_factors_module, alpha_id)
|
||||
expected = function(*(inputs[name] for name in call_inputs))
|
||||
actual = evaluate_phase4_formula(
|
||||
alpha_id,
|
||||
**{name: inputs[name] for name in reversed(call_inputs)},
|
||||
)
|
||||
pd.testing.assert_series_equal(actual, expected)
|
||||
|
||||
|
||||
def test_phase4_dispatch_rejects_unknown_missing_extra_and_non_series_inputs():
|
||||
inputs = _phase3_market_inputs()
|
||||
|
||||
with pytest.raises(KeyError, match="not registered"):
|
||||
evaluate_phase4_formula("alpha_050", close=inputs["close"])
|
||||
with pytest.raises(ValueError, match=r"missing inputs.*low"):
|
||||
evaluate_phase4_formula("alpha_051", high=inputs["high"])
|
||||
with pytest.raises(ValueError, match=r"unexpected inputs.*vwap"):
|
||||
evaluate_phase4_formula(
|
||||
"alpha_051",
|
||||
high=inputs["high"],
|
||||
low=inputs["low"],
|
||||
vwap=inputs["vwap"],
|
||||
)
|
||||
with pytest.raises(TypeError, match="high must be a pandas Series"):
|
||||
evaluate_phase4_formula( # type: ignore[arg-type]
|
||||
"alpha_051",
|
||||
high=[1.0, 2.0],
|
||||
low=inputs["low"],
|
||||
)
|
||||
|
||||
|
||||
def test_phase4_dispatch_rejects_implicit_series_alignment():
|
||||
inputs = _phase3_market_inputs()
|
||||
misaligned_low = inputs["low"].rename(index={79: 80})
|
||||
|
||||
with pytest.raises(ValueError, match="low index must align with high"):
|
||||
evaluate_phase4_formula(
|
||||
"alpha_051",
|
||||
high=inputs["high"],
|
||||
low=misaligned_low,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user