307 lines
13 KiB
Python
307 lines
13 KiB
Python
"""Offline exact-parity gate for the frozen Batch01 band/channel oracle."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import csv
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import numpy as np
|
|
|
|
from .historical_band_channel import OBSERVED_BAND_CHANNEL_PARAMS, evaluate_band_channel
|
|
from .parity_harness_v1 import (
|
|
ComparisonRecord,
|
|
FeatureVersion,
|
|
ParityStatus,
|
|
compare_array,
|
|
compare_float64_ulp,
|
|
recompute_coverage,
|
|
)
|
|
|
|
ROOT = Path(__file__).resolve().parents[3]
|
|
BATCH01_REQUEST = ROOT / "batch01_oracle_request.json"
|
|
BATCH01_RESULT = ROOT / "batch01_oracle_result.json"
|
|
BATCH01_OUTPUTS = ROOT / "batch01_oracle_outputs.npz"
|
|
CANONICAL_CSV = ROOT / "hs22_oracle_v1" / "binance_btcusdt_spot_2m_180d.csv"
|
|
BOLLINGER_RESOLUTION = ROOT / "batch01_bollinger_parity_resolution_v1.json"
|
|
BOLLINGER_ROOT_REPORT = ROOT / "batch01_native_mismatch_report.json"
|
|
|
|
_DIGESTS = {
|
|
"batch01_oracle_request.json": (
|
|
"7ca0c8578dd81c3ae60898d83e6e73f9bf8889a39a873b451c7cdaf036cbaf24"
|
|
),
|
|
"batch01_oracle_result.json": (
|
|
"7c1d5dbdcb183aa2d872baa45c4d02fe2d59cccabe9022f8797237b3776c8e96"
|
|
),
|
|
"batch01_oracle_outputs.npz": (
|
|
"e1f2268b634e50eb65b9b5175ea369e52bf0a2039b18049030849948a45fd149"
|
|
),
|
|
"hs22_oracle_v1/binance_btcusdt_spot_2m_180d.csv": (
|
|
"7fb56bb05b0edc2348407765cd693efadf334307c25877dfb52acfe4221e5d00"
|
|
),
|
|
}
|
|
|
|
|
|
class FrozenBatch01ArtifactError(ValueError):
|
|
"""Raised when a parity input is not the pinned local oracle artifact."""
|
|
|
|
|
|
def _digest(path: Path) -> str:
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
def _canonical_bytes(value: object) -> bytes:
|
|
return json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False).encode("utf-8")
|
|
|
|
|
|
def _array_digest(values: np.ndarray) -> str:
|
|
values = np.ascontiguousarray(values)
|
|
header = _canonical_bytes({"dtype": values.dtype.str, "shape": values.shape})
|
|
return hashlib.sha256(header + b"\0" + values.tobytes()).hexdigest()
|
|
|
|
|
|
def batch01_native_parity_manifest() -> dict[str, object]:
|
|
"""Return the reproducible exact-comparison contract for all 97 variants."""
|
|
return {
|
|
"schema_version": 1,
|
|
"artifact": "BATCH01_NATIVE_PARITY_MANIFEST_V1",
|
|
"engine_revision": "code-5056feb",
|
|
"frozen_inputs": {
|
|
name: {"path": name, "sha256": digest} for name, digest in _DIGESTS.items()
|
|
},
|
|
"input_columns": ["close", "high", "low", "volume"],
|
|
"variant_count": 97,
|
|
"comparison": {"dtype": "exact", "shape": "exact", "nan_mask": "exact", "values": "exact"},
|
|
"documented_exception": {
|
|
"path": BOLLINGER_RESOLUTION.name,
|
|
"scope": "float64 one-ULP cases listed for indicator IDs 17 and 18 only",
|
|
"global_tolerance": "forbidden",
|
|
},
|
|
"derived_state_transitions": {
|
|
"indicator_ids": [19, 28],
|
|
"state": "1 when close is greater than output, otherwise -1; warmup NaN is 0",
|
|
"transitions": "state differs from the preceding bar",
|
|
},
|
|
}
|
|
|
|
|
|
def batch01_native_parity_manifest_bytes() -> bytes:
|
|
return _canonical_bytes(batch01_native_parity_manifest()) + b"\n"
|
|
|
|
|
|
def write_batch01_native_parity_manifest(path: Path) -> None:
|
|
path.write_bytes(batch01_native_parity_manifest_bytes())
|
|
|
|
|
|
def _require_frozen_artifacts() -> tuple[dict[str, Any], dict[str, Any]]:
|
|
paths = {
|
|
"batch01_oracle_request.json": BATCH01_REQUEST,
|
|
"batch01_oracle_result.json": BATCH01_RESULT,
|
|
"batch01_oracle_outputs.npz": BATCH01_OUTPUTS,
|
|
"hs22_oracle_v1/binance_btcusdt_spot_2m_180d.csv": CANONICAL_CSV,
|
|
}
|
|
for name, path in paths.items():
|
|
if _digest(path) != _DIGESTS[name]:
|
|
raise FrozenBatch01ArtifactError(f"{name} digest does not match the frozen artifact")
|
|
request = json.loads(BATCH01_REQUEST.read_text(encoding="utf-8"))
|
|
result = json.loads(BATCH01_RESULT.read_text(encoding="utf-8"))
|
|
if request.get("artifact") != "HISTORICAL_FEATURE_ORACLE_V1_REQUEST":
|
|
raise FrozenBatch01ArtifactError("invalid Batch01 request artifact")
|
|
if result.get("artifact") != "HISTORICAL_FEATURE_ORACLE_V1_RESULT":
|
|
raise FrozenBatch01ArtifactError("invalid Batch01 result artifact")
|
|
if result.get("request_sha256") != _DIGESTS["batch01_oracle_request.json"]:
|
|
raise FrozenBatch01ArtifactError("result is not linked to the frozen request")
|
|
csv_digest = _DIGESTS["hs22_oracle_v1/binance_btcusdt_spot_2m_180d.csv"]
|
|
if result.get("input_csv_sha256") != csv_digest:
|
|
raise FrozenBatch01ArtifactError("result is not linked to the canonical CSV")
|
|
if result.get("outputs_npz", {}).get("sha256") != _DIGESTS["batch01_oracle_outputs.npz"]:
|
|
raise FrozenBatch01ArtifactError("result is not linked to the frozen NPZ")
|
|
return request, result
|
|
|
|
|
|
def _bollinger_resolution() -> dict[str, tuple[dict[str, Any], ...]]:
|
|
resolution = json.loads(BOLLINGER_RESOLUTION.read_text(encoding="utf-8"))
|
|
report = json.loads(BOLLINGER_ROOT_REPORT.read_text(encoding="utf-8"))
|
|
source = resolution.get("source_report", {})
|
|
exception = resolution.get("exception", {})
|
|
if (
|
|
resolution.get("artifact") != "BATCH01_BOLLINGER_PARITY_RESOLUTION_V1"
|
|
or resolution.get("schema_version") != 1
|
|
or source.get("artifact") != report.get("artifact")
|
|
or source.get("classification") != report.get("classification")
|
|
or source.get("failed_value_count") != report.get("failed_value_count")
|
|
or exception != {
|
|
"indicator_ids": [17, 18],
|
|
"dtype": "float64",
|
|
"max_ulps": 1,
|
|
"unlisted_mismatch_policy": "fail",
|
|
"all_other_indicators_policy": "exact",
|
|
"shape_policy": "exact",
|
|
"nan_mask_policy": "exact",
|
|
}
|
|
):
|
|
raise FrozenBatch01ArtifactError("invalid Bollinger parity resolution")
|
|
report_cases = {
|
|
(item["request_id"], int(detail["index"])): {
|
|
"indicator_id": item["indicator_id"],
|
|
"signed_ulp": detail["ulp"]["signed"],
|
|
"threshold_sensitivity": {
|
|
key: detail["threshold_sensitivity"][key]
|
|
for key in (
|
|
"oracle_close_above_band",
|
|
"native_close_above_band",
|
|
"decision_changed",
|
|
"close_to_oracle_band_ulps",
|
|
"close_to_native_band_ulps",
|
|
)
|
|
},
|
|
}
|
|
for item in report["variants"]
|
|
for detail in item["mismatches"]
|
|
}
|
|
accepted = resolution.get("accepted_cases")
|
|
if not isinstance(accepted, list) or len(accepted) != 8:
|
|
raise FrozenBatch01ArtifactError("resolution must list all eight accepted cases")
|
|
resolution_cases = {
|
|
(item["request_id"], int(item["index"])): {
|
|
"indicator_id": item["indicator_id"],
|
|
"signed_ulp": item["signed_ulp"],
|
|
"threshold_sensitivity": item["threshold_sensitivity"],
|
|
}
|
|
for item in accepted
|
|
}
|
|
if resolution_cases != report_cases or any(
|
|
int(item["indicator_id"]) not in {17, 18}
|
|
or item["threshold_sensitivity"]["decision_changed"]
|
|
for item in accepted
|
|
):
|
|
raise FrozenBatch01ArtifactError("resolution does not match the root-cause evidence")
|
|
grouped: dict[str, list[dict[str, Any]]] = {}
|
|
for item in accepted:
|
|
grouped.setdefault(item["request_id"], []).append(item)
|
|
return {request_id: tuple(cases) for request_id, cases in grouped.items()}
|
|
|
|
|
|
def _load_ohlcv() -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
with CANONICAL_CSV.open(newline="", encoding="utf-8") as handle:
|
|
rows = list(csv.DictReader(handle))
|
|
return tuple(
|
|
np.asarray([float(row[name]) for row in rows], dtype=np.float64)
|
|
for name in ("close", "high", "low", "volume")
|
|
) # type: ignore[return-value]
|
|
|
|
|
|
def _state_and_transitions(close: np.ndarray, output: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
|
state = np.zeros(output.shape, dtype=np.int8)
|
|
valid = ~np.isnan(output)
|
|
state[valid] = np.where(close[valid] > output[valid], 1, -1)
|
|
transitions = np.zeros(output.shape, dtype=np.bool_)
|
|
transitions[1:] = state[1:] != state[:-1]
|
|
return state, transitions
|
|
|
|
|
|
def _record_artifact(record: ComparisonRecord) -> dict[str, object]:
|
|
return record.artifact()
|
|
|
|
|
|
def batch01_native_acceptance_manifest(report: dict[str, object]) -> dict[str, object]:
|
|
"""Summarize the resolved Batch01 gate and recompute its submitted coverage."""
|
|
variants = [item for item in report["records"] if ":" not in item["name"]]
|
|
feature_versions = [
|
|
FeatureVersion(
|
|
feature_version=item["name"],
|
|
primitive_key=item["name"],
|
|
engineering_family="band_channel",
|
|
usage_slots=1,
|
|
status=ParityStatus(item["status"]),
|
|
)
|
|
for item in variants
|
|
]
|
|
accepted = [
|
|
item for item in variants if item["reason"] == "accepted_documented_float64_ulp_drift"
|
|
]
|
|
return {
|
|
"schema_version": 1,
|
|
"artifact": "BATCH01_NATIVE_ACCEPTANCE_MANIFEST_V1",
|
|
"parity_manifest": batch01_native_parity_manifest(),
|
|
"status": "accepted" if report["passed"] else "rejected",
|
|
"accepted_documented_ulp_variant_count": len(accepted),
|
|
"accepted_documented_ulp_value_count": sum(item["value_mismatches"] for item in accepted),
|
|
"coverage": recompute_coverage(feature_versions),
|
|
}
|
|
|
|
|
|
def write_batch01_native_acceptance_manifest(path: Path) -> None:
|
|
report = evaluate_batch01_native_parity()
|
|
path.write_bytes(_canonical_bytes(batch01_native_acceptance_manifest(report)) + b"\n")
|
|
|
|
|
|
def evaluate_batch01_native_parity() -> dict[str, object]:
|
|
"""Evaluate native Batch01 functions against the immutable oracle NPZ exactly."""
|
|
request, result = _require_frozen_artifacts()
|
|
accepted_cases = _bollinger_resolution()
|
|
requests = request["requests"]
|
|
result_requests = {item["request_id"]: item for item in result["requests"]}
|
|
requested_params = {
|
|
(int(item["indicator_id"]), int(item["period"]), float(item["p1"])) for item in requests
|
|
}
|
|
if requested_params != OBSERVED_BAND_CHANNEL_PARAMS or len(requests) != 97:
|
|
raise FrozenBatch01ArtifactError("request variants do not match the Batch01 native surface")
|
|
if set(result_requests) != {item["request_id"] for item in requests}:
|
|
raise FrozenBatch01ArtifactError("result request records do not match the frozen request")
|
|
close, high, low, volume = _load_ohlcv()
|
|
records: list[ComparisonRecord] = []
|
|
with np.load(BATCH01_OUTPUTS) as expected_outputs:
|
|
if set(expected_outputs.files) != set(result_requests):
|
|
raise FrozenBatch01ArtifactError("NPZ output keys do not match result request records")
|
|
for item in requests:
|
|
request_id = item["request_id"]
|
|
expected = expected_outputs[request_id]
|
|
metadata = result_requests[request_id]
|
|
if expected.dtype.str != metadata["dtype"] or list(expected.shape) != metadata["shape"]:
|
|
raise FrozenBatch01ArtifactError(f"NPZ metadata mismatch for {request_id}")
|
|
if _array_digest(expected) != metadata["sha256"]:
|
|
raise FrozenBatch01ArtifactError(f"NPZ value digest mismatch for {request_id}")
|
|
actual = evaluate_band_channel(
|
|
int(item["indicator_id"]),
|
|
close,
|
|
high,
|
|
low,
|
|
volume,
|
|
int(item["period"]),
|
|
float(item["p1"]),
|
|
)
|
|
indicator_id = int(item["indicator_id"])
|
|
cases = accepted_cases.get(request_id, ())
|
|
records.append(
|
|
compare_float64_ulp(
|
|
request_id,
|
|
expected,
|
|
actual,
|
|
max_ulps=1,
|
|
allowed_indexes=frozenset(int(case["index"]) for case in cases),
|
|
)
|
|
if indicator_id in {17, 18}
|
|
else compare_array(request_id, expected, actual)
|
|
)
|
|
if indicator_id in {19, 28}:
|
|
expected_state, expected_transitions = _state_and_transitions(close, expected)
|
|
actual_state, actual_transitions = _state_and_transitions(close, actual)
|
|
records.append(compare_array(f"{request_id}:state", expected_state, actual_state))
|
|
records.append(
|
|
compare_array(
|
|
f"{request_id}:transitions", expected_transitions, actual_transitions
|
|
)
|
|
)
|
|
failures = [record for record in records if not record.passed]
|
|
report = {
|
|
"manifest": batch01_native_parity_manifest(),
|
|
"records": [_record_artifact(record) for record in records],
|
|
"passed": not failures,
|
|
"failure_count": len(failures),
|
|
"failures": [_record_artifact(record) for record in failures],
|
|
}
|
|
report["acceptance_manifest"] = batch01_native_acceptance_manifest(report)
|
|
return report
|