215 lines
9.2 KiB
Python
215 lines
9.2 KiB
Python
|
|
"""Standalone runner for recovered historical ``hyperscalper.fast_engine``.
|
||
|
|
|
||
|
|
This file intentionally has no Artifex imports. Copy it with a request JSON,
|
||
|
|
CSV, and the recovered ``code-5056feb/src`` tree to run an external oracle.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import hashlib
|
||
|
|
import importlib
|
||
|
|
import json
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
|
||
|
|
ENGINE_REVISION = "code-5056feb"
|
||
|
|
ARTIFACT = "HISTORICAL_FEATURE_ORACLE_V1_RESULT"
|
||
|
|
REQUIRED_COLUMNS = ("close", "high", "low", "volume")
|
||
|
|
|
||
|
|
|
||
|
|
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:
|
||
|
|
contiguous = np.ascontiguousarray(values)
|
||
|
|
header = _canonical_bytes({"dtype": contiguous.dtype.str, "shape": contiguous.shape})
|
||
|
|
return _sha256_bytes(header + b"\0" + contiguous.tobytes())
|
||
|
|
|
||
|
|
|
||
|
|
def _load_ohlcv(path: Path, columns: tuple[str, ...]) -> tuple[np.ndarray, ...]:
|
||
|
|
# NumPy owns parsing so this runner has no CSV/dataframe dependency.
|
||
|
|
table = np.genfromtxt(path, delimiter=",", names=True, dtype=None, encoding="utf-8")
|
||
|
|
table = np.atleast_1d(table)
|
||
|
|
names = set(table.dtype.names or ())
|
||
|
|
missing = set(columns) - names
|
||
|
|
if missing:
|
||
|
|
raise ValueError(f"CSV is missing required columns: {', '.join(sorted(missing))}")
|
||
|
|
arrays = tuple(np.asarray(table[name], dtype=np.float64) for name in columns)
|
||
|
|
if not arrays[0].size or any(array.ndim != 1 or array.shape != arrays[0].shape for array in arrays):
|
||
|
|
raise ValueError("CSV OHLCV columns must be non-empty equally sized vectors")
|
||
|
|
return arrays
|
||
|
|
|
||
|
|
|
||
|
|
def _load_engine(source: Path):
|
||
|
|
package = source / "hyperscalper"
|
||
|
|
engine_file = package / "fast_engine.py"
|
||
|
|
if not engine_file.is_file() or not (package / "__init__.py").is_file():
|
||
|
|
raise ValueError("--recovered-source must contain hyperscalper/fast_engine.py from code-5056feb")
|
||
|
|
if any(
|
||
|
|
name in {"artifex", "control_plane"}
|
||
|
|
or name.startswith(("artifex.", "control_plane."))
|
||
|
|
for name in sys.modules
|
||
|
|
):
|
||
|
|
raise RuntimeError("Artifex modules must not be imported by the historical oracle")
|
||
|
|
sys.path.insert(0, str(source))
|
||
|
|
engine = importlib.import_module("hyperscalper.fast_engine")
|
||
|
|
if Path(engine.__file__).resolve() != engine_file.resolve():
|
||
|
|
raise RuntimeError("refused a hyperscalper.fast_engine outside --recovered-source")
|
||
|
|
return engine
|
||
|
|
|
||
|
|
|
||
|
|
def _requests(payload: dict[str, Any], known_ids: set[int]) -> list[dict[str, Any]]:
|
||
|
|
if payload.get("artifact") != "HISTORICAL_FEATURE_ORACLE_V1_REQUEST":
|
||
|
|
raise ValueError("request artifact must be HISTORICAL_FEATURE_ORACLE_V1_REQUEST")
|
||
|
|
if payload.get("schema_version") != 1 or payload.get("engine_revision") != ENGINE_REVISION:
|
||
|
|
raise ValueError("unsupported historical oracle request version or engine revision")
|
||
|
|
semantic_base = {
|
||
|
|
"engine_revision": payload["engine_revision"],
|
||
|
|
"input_columns": payload.get("input_columns"),
|
||
|
|
"output_dtype": payload.get("output_dtype"),
|
||
|
|
"window_policy": payload.get("window_policy"),
|
||
|
|
}
|
||
|
|
if semantic_base["input_columns"] != list(REQUIRED_COLUMNS) or semantic_base["output_dtype"] != "float64":
|
||
|
|
raise ValueError("request has unsupported input or output semantics")
|
||
|
|
rows = payload.get("requests")
|
||
|
|
if not isinstance(rows, list) or not rows:
|
||
|
|
raise ValueError("request must contain a non-empty requests list")
|
||
|
|
ids: set[str] = set()
|
||
|
|
parsed: list[dict[str, Any]] = []
|
||
|
|
for row in rows:
|
||
|
|
if not isinstance(row, dict):
|
||
|
|
raise ValueError("every request must be an object")
|
||
|
|
request_id = row.get("request_id")
|
||
|
|
if not isinstance(request_id, str) or not request_id or request_id in ids:
|
||
|
|
raise ValueError("request_id must be a unique non-empty string")
|
||
|
|
indicator_id, period, p1 = int(row["indicator_id"]), int(row["period"]), float(row["p1"])
|
||
|
|
if indicator_id not in known_ids:
|
||
|
|
raise ValueError(f"unknown historical indicator ID: {indicator_id}")
|
||
|
|
if period <= 0 or not np.isfinite(p1):
|
||
|
|
raise ValueError(f"invalid period or p1 for {request_id}")
|
||
|
|
fingerprint = row.get("semantic_fingerprint")
|
||
|
|
expected_fingerprint = _sha256_bytes(
|
||
|
|
_canonical_bytes(
|
||
|
|
{**semantic_base, "indicator_id": indicator_id, "period": period, "p1": p1}
|
||
|
|
)
|
||
|
|
)
|
||
|
|
if fingerprint != expected_fingerprint:
|
||
|
|
raise ValueError(f"semantic fingerprint mismatch for {request_id}")
|
||
|
|
ids.add(request_id)
|
||
|
|
parsed.append(
|
||
|
|
{
|
||
|
|
"request_id": request_id,
|
||
|
|
"indicator_id": indicator_id,
|
||
|
|
"period": period,
|
||
|
|
"p1": p1,
|
||
|
|
"semantic_fingerprint": fingerprint,
|
||
|
|
}
|
||
|
|
)
|
||
|
|
return parsed
|
||
|
|
|
||
|
|
|
||
|
|
def _windows(payload: dict[str, Any], length: int) -> list[dict[str, int | str]]:
|
||
|
|
rows = payload.get("windows", [{"window_id": "full", "start": 0, "stop": length}])
|
||
|
|
if not isinstance(rows, list) or not rows:
|
||
|
|
raise ValueError("windows must be a non-empty list")
|
||
|
|
windows = []
|
||
|
|
for row in rows:
|
||
|
|
if not isinstance(row, dict):
|
||
|
|
raise ValueError("every window must be an object")
|
||
|
|
window_id, start, stop = str(row.get("window_id", "")), int(row["start"]), int(row["stop"])
|
||
|
|
if not window_id or start < 0 or stop <= start or stop > length:
|
||
|
|
raise ValueError(f"invalid output window: {window_id}")
|
||
|
|
windows.append({"window_id": window_id, "start": start, "stop": stop})
|
||
|
|
return windows
|
||
|
|
|
||
|
|
|
||
|
|
def run(request_path: Path, csv_path: Path, output_dir: Path, recovered_source: Path) -> Path:
|
||
|
|
payload = json.loads(request_path.read_text(encoding="utf-8"))
|
||
|
|
if not isinstance(payload, dict):
|
||
|
|
raise ValueError("request JSON must be an object")
|
||
|
|
expected_csv_hash = payload.get("input_csv_sha256")
|
||
|
|
csv_hash = _sha256_bytes(csv_path.read_bytes())
|
||
|
|
if expected_csv_hash is not None and expected_csv_hash != csv_hash:
|
||
|
|
raise ValueError("input CSV SHA-256 does not match the request")
|
||
|
|
columns = tuple(payload.get("input_columns", REQUIRED_COLUMNS))
|
||
|
|
if columns != REQUIRED_COLUMNS:
|
||
|
|
raise ValueError("only close, high, low, volume input semantics are supported")
|
||
|
|
close, high, low, volume = _load_ohlcv(csv_path, columns)
|
||
|
|
engine = _load_engine(recovered_source)
|
||
|
|
requests = _requests(payload, {int(row[0]) for row in engine.FAST_POOL})
|
||
|
|
windows = _windows(payload, len(close))
|
||
|
|
outputs: dict[str, np.ndarray] = {}
|
||
|
|
records = []
|
||
|
|
for row in requests:
|
||
|
|
values = np.asarray(
|
||
|
|
engine.compute(
|
||
|
|
row["indicator_id"], close, high, low, volume, row["period"], row["p1"]
|
||
|
|
),
|
||
|
|
dtype=np.float64,
|
||
|
|
)
|
||
|
|
# Validate the artifact against a second direct call to the recovered dispatcher.
|
||
|
|
direct = np.asarray(
|
||
|
|
engine.compute(
|
||
|
|
row["indicator_id"], close, high, low, volume, row["period"], row["p1"]
|
||
|
|
),
|
||
|
|
dtype=np.float64,
|
||
|
|
)
|
||
|
|
if values.shape != close.shape or not np.array_equal(values, direct, equal_nan=True):
|
||
|
|
raise RuntimeError(f"direct compute self-validation failed for {row['request_id']}")
|
||
|
|
outputs[row["request_id"]] = values
|
||
|
|
records.append(
|
||
|
|
{
|
||
|
|
**row,
|
||
|
|
"dtype": values.dtype.str,
|
||
|
|
"shape": list(values.shape),
|
||
|
|
"sha256": _array_sha256(values),
|
||
|
|
"window_sha256": {
|
||
|
|
window["window_id"]: _array_sha256(values[window["start"] : window["stop"]])
|
||
|
|
for window in windows
|
||
|
|
},
|
||
|
|
}
|
||
|
|
)
|
||
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||
|
|
npz_path = output_dir / "historical_feature_oracle_v1_outputs.npz"
|
||
|
|
np.savez_compressed(npz_path, **outputs)
|
||
|
|
result_path = output_dir / "historical_feature_oracle_v1_result.json"
|
||
|
|
result = {
|
||
|
|
"schema_version": 1,
|
||
|
|
"artifact": ARTIFACT,
|
||
|
|
"engine_revision": ENGINE_REVISION,
|
||
|
|
"request_sha256": _sha256_bytes(request_path.read_bytes()),
|
||
|
|
"input_csv_sha256": csv_hash,
|
||
|
|
"engine_sha256": _sha256_bytes(
|
||
|
|
(recovered_source / "hyperscalper" / "fast_engine.py").read_bytes()
|
||
|
|
),
|
||
|
|
"row_count": len(close),
|
||
|
|
"windows": windows,
|
||
|
|
"outputs_npz": {"path": npz_path.name, "sha256": _sha256_bytes(npz_path.read_bytes())},
|
||
|
|
"requests": records,
|
||
|
|
}
|
||
|
|
result_path.write_bytes(_canonical_bytes(result) + b"\n")
|
||
|
|
return result_path
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> None:
|
||
|
|
parser = argparse.ArgumentParser(description="Run recovered code-5056feb historical features offline.")
|
||
|
|
parser.add_argument("--request", type=Path, required=True)
|
||
|
|
parser.add_argument("--input-csv", type=Path, required=True)
|
||
|
|
parser.add_argument("--output-dir", type=Path, required=True)
|
||
|
|
parser.add_argument("--recovered-source", type=Path, required=True)
|
||
|
|
args = parser.parse_args()
|
||
|
|
print(run(args.request, args.input_csv, args.output_dir, args.recovered_source))
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|