"""GPU_FEATURE_PARITY_CONTRACT_V1_1 calibration and frozen-contract validation. Calibration is evidence production only. Validation never measures or derives limits: it accepts a supplied, immutable contract and enforces its limits. """ from __future__ import annotations import csv import hashlib import json from pathlib import Path from typing import Any, Mapping import numpy as np from control_plane.trading_studio.indicators.historical_band_channel import evaluate_band_channel ARTIFACT = "GPU_FEATURE_PARITY_CONTRACT_V1_1" CALIBRATION_ARTIFACT = "GPU_FEATURE_PARITY_CALIBRATION_V1_1" VALIDATION_ARTIFACT = "GPU_FEATURE_PARITY_VALIDATION_V1_1" CORPUS_ARTIFACT = "GPU_FEATURE_PARITY_CORPUS_MANIFEST_V1_1" NAN_GAP_SEMANTICS_ARTIFACT = "GPU_FEATURE_PARITY_NAN_GAP_SEMANTICS_V1_1" CPU_ORACLE_ARTIFACT = "GPU_FEATURE_PARITY_CPU_ORACLE_V1_1" FAMILIES = {17: "bollinger", 18: "bollinger", 19: "supertrend", 20: "donchian", 21: "donchian", 23: "keltner", 24: "keltner", 28: "psar"} SCENARIOS = ( "warmup", "constant", "nearly_constant_tiny_variance", "high_variance", "alternating", "uptrend", "downtrend", "flat_breakout", "breakout_flat", "threshold_equality", "threshold_one_ulp_above", "threshold_one_ulp_below", "repeated_equality", "zero_range", "tiny_range", "abrupt_atr", ) TRACE_KEYS = { "supertrend": frozenset(("output", "atr", "upper", "lower", "direction", "initialized")), "psar": frozenset(("output", "bullish", "extreme", "acceleration", "reversal")), } class FrozenContractError(ValueError): pass def canonical_bytes(value: object) -> bytes: return json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False).encode("utf-8") def sha256_bytes(value: bytes) -> str: return hashlib.sha256(value).hexdigest() def array_sha256(values: np.ndarray) -> str: values = np.ascontiguousarray(values) return sha256_bytes(canonical_bytes({"dtype": values.dtype.str, "shape": values.shape}) + b"\0" + values.tobytes()) def write_artifact(path: Path, payload: Mapping[str, Any]) -> None: path.write_bytes(canonical_bytes(dict(payload)) + b"\n") def deterministic_adversarial_ohlcv(*, length: int = 256, seed: int = 0) -> dict[str, np.ndarray]: """Generate reproducible valid OHLCV covering all declared edge categories.""" if length < len(SCENARIOS) * 4: raise ValueError("length must allow every adversarial scenario") rng = np.random.default_rng(seed) block = max(4, length // len(SCENARIOS)) close = np.empty(length, dtype=np.float64) spread = 0.15 + rng.random(length) * 0.2 level = 100.0 for number, scenario in enumerate(SCENARIOS): start, stop = number * block, length if number == len(SCENARIOS) - 1 else min(length, (number + 1) * block) count = stop - start if scenario == "warmup": values = level + np.arange(count) * 0.03 elif scenario in {"constant", "repeated_equality"}: values = np.full(count, level) elif scenario == "nearly_constant_tiny_variance": values = level + np.resize(np.array([0.0, np.spacing(level)]), count) elif scenario == "high_variance": values = level + np.cumsum(rng.normal(0.0, 3.0, count)) elif scenario == "alternating": values = level + np.resize(np.array([0.75, -0.75]), count) elif scenario == "uptrend": values = level + np.arange(1, count + 1) * 0.18 elif scenario == "downtrend": values = level - np.arange(1, count + 1) * 0.18 elif scenario == "flat_breakout": values = np.full(count, level); values[count // 2 :] += 4.0 elif scenario == "breakout_flat": values = np.full(count, level + 4.0) elif scenario == "threshold_equality": values = np.full(count, level) elif scenario == "threshold_one_ulp_above": values = np.full(count, np.nextafter(level, np.inf)) elif scenario == "threshold_one_ulp_below": values = np.full(count, np.nextafter(level, -np.inf)) elif scenario == "zero_range": values = np.full(count, level); spread[start:stop] = 0.0 elif scenario == "tiny_range": values = np.full(count, level); spread[start:stop] = np.spacing(level) elif scenario == "abrupt_atr": values = level + np.cumsum(np.resize(np.array([0.01, -0.01]), count)); spread[start:stop] = np.resize(np.array([0.01, 5.0]), count) close[start:stop] = values level = values[-1] high, low = close + spread, close - spread volume = 1_000.0 + rng.integers(0, 10_000, length).astype(np.float64) return {"close": close.astype(np.float64), "high": high.astype(np.float64), "low": low.astype(np.float64), "volume": volume} def corpus_manifest(corpora: Mapping[str, Mapping[str, np.ndarray]], provenance: Mapping[str, Any]) -> dict[str, Any]: entries = [] for name, ohlcv in sorted(corpora.items()): if set(ohlcv) != {"close", "high", "low", "volume"}: raise ValueError("corpus must contain exactly close, high, low, volume") entries.append({"name": name, "columns": {key: array_sha256(value) for key, value in sorted(ohlcv.items())}, "row_count": int(len(ohlcv["close"]))}) return {"artifact": CORPUS_ARTIFACT, "schema_version": "1.1", "scenario_categories": list(SCENARIOS), "corpora": entries, "provenance": dict(provenance)} def nan_gap_semantics_manifest() -> dict[str, Any]: """Record the deliberate non-finite-input policy separately from parity data.""" return {"artifact": NAN_GAP_SEMANTICS_ARTIFACT, "schema_version": "1.1", "historical_input_policy": "reject_non_finite_ohlcv", "internal_nan_gap_semantics": "undefined", "validation": "input_rejection_before_historical_evaluation"} def _read_csv(path: Path) -> dict[str, np.ndarray]: with path.open(newline="", encoding="utf-8") as handle: rows = list(csv.DictReader(handle)) if not rows or not {"close", "high", "low", "volume"}.issubset(rows[0]): raise ValueError("CSV must contain non-empty close, high, low, volume columns") ohlcv = {key: np.asarray([float(row[key]) for row in rows], dtype=np.float64) for key in ("close", "high", "low", "volume")} if not all(np.isfinite(values).all() for values in ohlcv.values()): raise ValueError("historical NaN/gap semantics are undefined; non-finite OHLCV inputs are rejected") return ohlcv def historical_cpu_oracle(csv_path: Path, requests: list[Mapping[str, Any]]) -> dict[str, Any]: """Evaluate arbitrary CSV inputs through the historical Batch01 source port.""" ohlcv = _read_csv(csv_path) outputs: dict[str, np.ndarray] = {} for request in requests: identifier = str(request["request_id"]) indicator = int(request["indicator_id"]) if indicator not in FAMILIES or identifier in outputs: raise ValueError("requests must have unique supported request IDs") outputs[identifier] = evaluate_band_channel(indicator, **ohlcv, period=int(request["period"]), p1=float(request["p1"])) return {"artifact": CPU_ORACLE_ARTIFACT, "schema_version": "1.1", "input_csv_sha256": sha256_bytes(csv_path.read_bytes()), "source": {"module": "control_plane.trading_studio.indicators.historical_band_channel", "evaluator": "evaluate_band_channel"}, "outputs": outputs, "output_sha256": {name: array_sha256(value) for name, value in outputs.items()}} def _continuity_decisions(close: np.ndarray, values: np.ndarray) -> dict[str, np.ndarray]: valid = np.isfinite(close) & np.isfinite(values) state = np.zeros(values.shape, dtype=np.int8) state[valid] = np.sign(close[valid] - values[valid]).astype(np.int8) adjacent = valid[1:] & valid[:-1] transition = np.zeros(values.shape, dtype=np.bool_) transition[1:] = adjacent & (state[1:] != state[:-1]) above = np.zeros(values.shape, dtype=np.bool_) below = np.zeros(values.shape, dtype=np.bool_) above[1:] = adjacent & (state[:-1] <= 0) & (state[1:] > 0) below[1:] = adjacent & (state[:-1] >= 0) & (state[1:] < 0) return {"valid": valid, "state": state, "transitions": transition, "crossings_above": above, "crossings_below": below} def cpu_supertrend_trace(close: np.ndarray, high: np.ndarray, low: np.ndarray, period: int, multiplier: float) -> dict[str, np.ndarray]: from control_plane.trading_studio.indicators.historical_band_channel import _rma, _true_range n = len(close); atr = _rma(_true_range(high, low, close), period) output = np.full(n, np.nan); upper = output.copy(); lower = output.copy(); direction = np.zeros(n, np.int8); initialized = np.zeros(n, bool) for i in range(period, n): if np.isnan(atr[i]): continue basic_upper = (high[i] + low[i]) / 2 + multiplier * atr[i]; basic_lower = (high[i] + low[i]) / 2 - multiplier * atr[i] if i == period: upper[i], lower[i] = basic_upper, basic_lower; output[i] = lower[i] if close[i] > lower[i] else upper[i] else: upper[i] = min(basic_upper, upper[i - 1]) if close[i - 1] <= upper[i - 1] else basic_upper lower[i] = max(basic_lower, lower[i - 1]) if close[i - 1] >= lower[i - 1] else basic_lower output[i] = lower[i] if direction[i - 1] == 1 and close[i] >= lower[i] else upper[i] if direction[i - 1] == 1 else upper[i] if close[i] <= upper[i] else lower[i] direction[i] = 1 if close[i] > output[i] else -1; initialized[i] = True return {"output": output, "atr": atr, "upper": upper, "lower": lower, "direction": direction, "initialized": initialized} def cpu_psar_trace(close: np.ndarray, high: np.ndarray, low: np.ndarray, step: float) -> dict[str, np.ndarray]: n = len(close); output = np.full(n, np.nan); bullish = np.zeros(n, bool); extreme = np.full(n, np.nan); acceleration = np.full(n, np.nan); reversal = np.zeros(n, bool) if n < 2: return {"output": output, "bullish": bullish, "extreme": extreme, "acceleration": acceleration, "reversal": reversal} bull, af, ep, sar = True, .02, high[0], low[0] for i in range(1, n): sar += af * (ep - sar); sar = min(sar, low[i - 1], low[i - 2] if i >= 2 else low[i - 1]) if bull else max(sar, high[i - 1], high[i - 2] if i >= 2 else high[i - 1]) reverse = (bull and low[i] < sar) or (not bull and high[i] > sar) if reverse: bull, sar, ep, af = (False, ep, low[i], .02) if bull else (True, ep, high[i], .02) elif (bull and high[i] > ep) or (not bull and low[i] < ep): ep = high[i] if bull else low[i]; af = min(af + step, .2) output[i], bullish[i], extreme[i], acceleration[i], reversal[i] = sar, bull, ep, af, reverse return {"output": output, "bullish": bullish, "extreme": extreme, "acceleration": acceleration, "reversal": reversal} def compare_stateful_trace(expected: Mapping[str, np.ndarray], actual: Mapping[str, np.ndarray], *, trace_type: str | None = None) -> dict[str, Any]: if trace_type is not None and trace_type not in TRACE_KEYS: raise ValueError("unknown stateful trace type") keys = sorted(set(expected) | set(actual)); failures = [] required = TRACE_KEYS.get(trace_type, frozenset(keys)) if set(expected) != required or set(actual) != required: failures.extend(sorted(required - set(expected) | required - set(actual))) for key in keys: if key not in expected or key not in actual or not np.array_equal(expected[key], actual[key], equal_nan=True): failures.append(key) return {"exact": not failures, "required_keys": sorted(required), "mismatched_keys": sorted(set(failures))} def _ulp_distances(actual: np.ndarray, expected: np.ndarray) -> np.ndarray: """Return finite IEEE-754 float64 distances, treating signed zero as equal.""" actual64, expected64 = np.asarray(actual, dtype=np.float64), np.asarray(expected, dtype=np.float64) actual_bits, expected_bits = actual64.view(np.uint64), expected64.view(np.uint64) actual_ordered = np.where(actual_bits >> 63 != 0, ~actual_bits, actual_bits | np.uint64(1 << 63)) expected_ordered = np.where(expected_bits >> 63 != 0, ~expected_bits, expected_bits | np.uint64(1 << 63)) distances = np.asarray(np.abs(actual_ordered.astype(object) - expected_ordered.astype(object)), dtype=np.float64) distances[actual64 == expected64] = 0.0 return distances def _numeric_metrics(actual: np.ndarray, expected: np.ndarray) -> dict[str, float]: finite = np.isfinite(actual) & np.isfinite(expected) delta = np.abs(actual[finite] - expected[finite]) finite_expected = expected[finite] relative = delta[np.abs(finite_expected) != 0] / np.abs(finite_expected[np.abs(finite_expected) != 0]) ulps = _ulp_distances(actual[finite], expected[finite]) return {"max_absolute_error": float(delta.max()) if len(delta) else 0.0, "max_relative_error": float(relative.max()) if len(relative) else 0.0, "mae": float(delta.mean()) if len(delta) else 0.0, "max_ulp": float(ulps.max()) if len(ulps) else 0.0, "p50_ulp": float(np.percentile(ulps, 50)) if len(ulps) else 0.0, "p95_ulp": float(np.percentile(ulps, 95)) if len(ulps) else 0.0, "p99_ulp": float(np.percentile(ulps, 99)) if len(ulps) else 0.0, "p99_9_ulp": float(np.percentile(ulps, 99.9)) if len(ulps) else 0.0} def calibrate(outputs: Mapping[str, np.ndarray], oracle: Mapping[str, np.ndarray], requests: list[Mapping[str, Any]], close: np.ndarray, corpus: Mapping[str, Any]) -> dict[str, Any]: """Serialize observed evidence; it is intentionally not an acceptance contract.""" records = [] for request in requests: key = str(request["request_id"]); actual, expected = outputs[key], oracle[key] metrics = _numeric_metrics(actual, expected) if actual.shape == expected.shape else _numeric_metrics(np.array([]), np.array([])) records.append({"request_id": key, "family": FAMILIES[int(request["indicator_id"])], "shape": list(expected.shape), "dtype": expected.dtype.str, "nan_mask_exact": bool(actual.shape == expected.shape and np.array_equal(np.isnan(actual), np.isnan(expected))), **metrics, "decision": _continuity_decisions(close, expected), "actual_decision": _continuity_decisions(close, actual)}) # Decision vectors are retained as hashable evidence rather than JSON arrays. for record in records: record["decision_sha256"] = sha256_bytes(b"".join(array_sha256(value).encode() for value in record.pop("decision").values())) record["actual_decision_sha256"] = sha256_bytes(b"".join(array_sha256(value).encode() for value in record.pop("actual_decision").values())) return {"artifact": CALIBRATION_ARTIFACT, "schema_version": "1.1", "corpus": dict(corpus), "records": records} def freeze_contract(calibration: Mapping[str, Any]) -> dict[str, Any]: if calibration.get("artifact") != CALIBRATION_ARTIFACT: raise FrozenContractError("only V1_1 calibration evidence can be frozen") fields = ("max_absolute_error", "max_relative_error", "mae", "max_ulp", "p50_ulp", "p95_ulp", "p99_ulp", "p99_9_ulp") limits = {record["request_id"]: {key: record[key] for key in ("family", "shape", "dtype", "nan_mask_exact", *fields, "decision_sha256")} for record in calibration["records"]} family_limits = {family: {field: max(record[field] for record in calibration["records"] if record["family"] == family) for field in fields} for family in sorted({record["family"] for record in calibration["records"]})} return {"artifact": ARTIFACT, "schema_version": "1.1", "status": "frozen", "calibration_sha256": sha256_bytes(canonical_bytes(calibration)), "corpus": calibration["corpus"], "family_limits": family_limits, "feature_limits": limits} def validate_frozen_contract(contract: Mapping[str, Any], outputs: Mapping[str, np.ndarray], oracle: Mapping[str, np.ndarray], close: np.ndarray) -> dict[str, Any]: """Fail closed against a supplied frozen contract; no limits are calculated here.""" if contract.get("artifact") != ARTIFACT or contract.get("schema_version") != "1.1" or contract.get("status") != "frozen": raise FrozenContractError("provided contract is not frozen GPU_FEATURE_PARITY_CONTRACT_V1_1") limits, family_limits = contract.get("feature_limits"), contract.get("family_limits") if not isinstance(limits, dict) or not isinstance(family_limits, dict) or set(limits) != set(oracle) or set(outputs) != set(oracle): raise FrozenContractError("contract, oracle, and GPU outputs must name exactly the same features") records = [] for key, expected in oracle.items(): actual, limit = outputs[key], limits[key] measured = _numeric_metrics(actual, expected) if actual.shape == expected.shape else _numeric_metrics(np.array([]), np.array([])) decisions = _continuity_decisions(close, expected) gpu_decisions = _continuity_decisions(close, actual) if actual.shape == close.shape else None decision_hash = sha256_bytes(b"".join(array_sha256(value).encode() for value in decisions.values())) family = family_limits.get(limit.get("family")) numeric_violations = [name for name, value in measured.items() if not family or name not in limit or name not in family or value > float(limit[name]) or value > float(family[name])] numeric_ok = not numeric_violations passed = bool(actual.shape == tuple(limit["shape"]) == expected.shape and actual.dtype.str == limit["dtype"] == expected.dtype.str and np.array_equal(np.isnan(actual), np.isnan(expected)) and numeric_ok and decision_hash == limit["decision_sha256"] and gpu_decisions is not None and all(np.array_equal(decisions[name], gpu_decisions[name]) for name in decisions)) records.append({"request_id": key, "passed": passed, "numeric_violations": numeric_violations, **measured}) return {"artifact": VALIDATION_ARTIFACT, "schema_version": "1.1", "contract_sha256": sha256_bytes(canonical_bytes(contract)), "passed": all(item["passed"] for item in records), "records": records}