Merge remote-tracking branch 'origin/main' into codex/research-alpha158-phase6-formula-con (#13)
CI / lite (push) Successful in 7s
CI / lite (push) Successful in 7s
This commit was merged in pull request #13.
This commit is contained in:
@@ -20,6 +20,9 @@ from quant_engine.alpha_factors import (
|
||||
ALPHA158_PHASE5_FORMULA_CATALOG_SHA256,
|
||||
ALPHA158_PHASE5_FORMULA_CONTRACT_VERSION,
|
||||
ALPHA158_PHASE5_FORMULA_SPECS,
|
||||
ALPHA158_PHASE6_FORMULA_CATALOG_SHA256,
|
||||
ALPHA158_PHASE6_FORMULA_CONTRACT_VERSION,
|
||||
ALPHA158_PHASE6_FORMULA_SPECS,
|
||||
alpha_001,
|
||||
alpha_002,
|
||||
alpha_003,
|
||||
@@ -183,11 +186,13 @@ from quant_engine.alpha_factors import (
|
||||
evaluate_phase3_formula,
|
||||
evaluate_phase4_formula,
|
||||
evaluate_phase5_formula,
|
||||
evaluate_phase6_formula,
|
||||
list_phase1_operators,
|
||||
list_phase2_operators,
|
||||
list_phase3_formulas,
|
||||
list_phase4_formulas,
|
||||
list_phase5_formulas,
|
||||
list_phase6_formulas,
|
||||
correlation,
|
||||
covariance,
|
||||
decay_linear,
|
||||
@@ -1962,3 +1967,193 @@ def test_phase5_contract_is_publicly_exported():
|
||||
"list_phase5_formulas",
|
||||
"evaluate_phase5_formula",
|
||||
} <= set(alpha_factors_module.__all__)
|
||||
|
||||
|
||||
# ── Alpha158 Phase 6: versioned alpha151-alpha158 formula contract ──────────
|
||||
|
||||
|
||||
def test_phase6_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(151, 159))
|
||||
expected_fields = {
|
||||
"name",
|
||||
"contract_version",
|
||||
"formula",
|
||||
"category",
|
||||
"complexity",
|
||||
"parameters",
|
||||
"description",
|
||||
"references",
|
||||
"call_inputs",
|
||||
"formula_inputs",
|
||||
"input_category",
|
||||
}
|
||||
|
||||
assert ALPHA158_PHASE6_FORMULA_CONTRACT_VERSION == "1.0.0"
|
||||
assert list_phase6_formulas() == expected_ids
|
||||
assert tuple(ALPHA158_PHASE6_FORMULA_SPECS) == expected_ids
|
||||
assert Counter(
|
||||
spec["input_category"] for spec in ALPHA158_PHASE6_FORMULA_SPECS.values()
|
||||
) == {"pair": 6, "triple": 2}
|
||||
|
||||
for alpha_id, spec in ALPHA158_PHASE6_FORMULA_SPECS.items():
|
||||
assert set(spec) == expected_fields
|
||||
assert spec["name"] == alpha_id
|
||||
assert spec["contract_version"] == ALPHA158_PHASE6_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_PHASE6_FORMULA_SPECS.items()
|
||||
}
|
||||
encoded = json.dumps(
|
||||
serializable_specs,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
ensure_ascii=False,
|
||||
).encode()
|
||||
assert hashlib.sha256(encoded).hexdigest() == ALPHA158_PHASE6_FORMULA_CATALOG_SHA256
|
||||
assert ALPHA158_PHASE6_FORMULA_CATALOG_SHA256 == (
|
||||
"70ffae16ca6cbb59a1e8644f5cdeec1d291fa75ff8b5647fb534af0083408219"
|
||||
)
|
||||
|
||||
|
||||
def test_phase6_formula_catalog_is_recursively_immutable():
|
||||
import operator
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
operator.setitem(ALPHA158_PHASE6_FORMULA_SPECS, "alpha_151", {})
|
||||
with pytest.raises(TypeError):
|
||||
operator.setitem(
|
||||
ALPHA158_PHASE6_FORMULA_SPECS["alpha_151"],
|
||||
"formula",
|
||||
"changed",
|
||||
)
|
||||
with pytest.raises(TypeError):
|
||||
operator.setitem(
|
||||
ALPHA158_PHASE6_FORMULA_SPECS["alpha_151"]["call_inputs"],
|
||||
0,
|
||||
"volume",
|
||||
)
|
||||
|
||||
|
||||
def test_phase6_catalog_freezes_callable_and_formula_inputs():
|
||||
import inspect
|
||||
|
||||
for alpha_id, spec in ALPHA158_PHASE6_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_PHASE6_FORMULA_SPECS["alpha_158"]["call_inputs"] == (
|
||||
"high",
|
||||
"low",
|
||||
"volume",
|
||||
)
|
||||
|
||||
|
||||
def test_phase6_dispatch_matches_all_existing_alpha151_alpha158_functions():
|
||||
inputs = _phase3_market_inputs()
|
||||
|
||||
for alpha_id, spec in ALPHA158_PHASE6_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_phase6_formula(
|
||||
alpha_id,
|
||||
**{name: inputs[name] for name in reversed(call_inputs)},
|
||||
)
|
||||
pd.testing.assert_series_equal(actual, expected)
|
||||
|
||||
|
||||
def test_phase6_dispatch_rejects_unknown_missing_extra_and_non_series_inputs():
|
||||
inputs = _phase3_market_inputs()
|
||||
|
||||
with pytest.raises(KeyError, match="not registered"):
|
||||
evaluate_phase6_formula("alpha_150", close=inputs["close"])
|
||||
with pytest.raises(KeyError, match="not registered"):
|
||||
evaluate_phase6_formula("alpha_159", close=inputs["close"])
|
||||
with pytest.raises(ValueError, match=r"missing inputs.*volume"):
|
||||
evaluate_phase6_formula("alpha_151", close=inputs["close"])
|
||||
with pytest.raises(ValueError, match=r"unexpected inputs.*vwap"):
|
||||
evaluate_phase6_formula(
|
||||
"alpha_151",
|
||||
close=inputs["close"],
|
||||
volume=inputs["volume"],
|
||||
vwap=inputs["vwap"],
|
||||
)
|
||||
with pytest.raises(TypeError, match="volume must be a pandas Series"):
|
||||
evaluate_phase6_formula( # type: ignore[arg-type]
|
||||
"alpha_151",
|
||||
close=inputs["close"],
|
||||
volume=[1.0, 2.0],
|
||||
)
|
||||
|
||||
|
||||
def test_phase6_dispatch_rejects_length_and_index_alignment_errors():
|
||||
inputs = _phase3_market_inputs()
|
||||
shorter_volume = inputs["volume"].iloc[:-1]
|
||||
misaligned_low = inputs["low"].rename(index={79: 80})
|
||||
|
||||
with pytest.raises(ValueError, match="volume length must match close"):
|
||||
evaluate_phase6_formula(
|
||||
"alpha_151",
|
||||
close=inputs["close"],
|
||||
volume=shorter_volume,
|
||||
)
|
||||
with pytest.raises(ValueError, match="low index must align with high"):
|
||||
evaluate_phase6_formula(
|
||||
"alpha_158",
|
||||
high=inputs["high"],
|
||||
low=misaligned_low,
|
||||
volume=inputs["volume"],
|
||||
)
|
||||
|
||||
|
||||
def test_phase6_preserves_complete_alpha001_alpha158_formula_body_fingerprint():
|
||||
import ast
|
||||
import hashlib
|
||||
import inspect
|
||||
import json
|
||||
import textwrap
|
||||
|
||||
fingerprints = {}
|
||||
for number in range(1, 159):
|
||||
alpha_id = f"alpha_{number:03d}"
|
||||
source = textwrap.dedent(inspect.getsource(getattr(alpha_factors_module, alpha_id)))
|
||||
node = ast.parse(source).body[0]
|
||||
assert isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
||||
body = ast.dump(
|
||||
ast.Module(body=node.body, type_ignores=[]),
|
||||
include_attributes=False,
|
||||
)
|
||||
fingerprints[alpha_id] = hashlib.sha256(body.encode()).hexdigest()
|
||||
|
||||
encoded = json.dumps(
|
||||
fingerprints,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
).encode()
|
||||
assert hashlib.sha256(encoded).hexdigest() == (
|
||||
"f3ae807983cf8ca083d0b924c3807ffd84a62a5bd354f9efb1a1a16ca40c7da6"
|
||||
)
|
||||
|
||||
|
||||
def test_phase6_contract_is_publicly_exported():
|
||||
assert {
|
||||
"ALPHA158_PHASE6_FORMULA_CONTRACT_VERSION",
|
||||
"ALPHA158_PHASE6_FORMULA_CATALOG_SHA256",
|
||||
"ALPHA158_PHASE6_FORMULA_SPECS",
|
||||
"list_phase6_formulas",
|
||||
"evaluate_phase6_formula",
|
||||
} <= set(alpha_factors_module.__all__)
|
||||
|
||||
Reference in New Issue
Block a user