Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions scripts/fingerprint_self_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"""
from __future__ import annotations

import ast
import base64
import importlib.util
import json
Expand Down Expand Up @@ -146,6 +147,49 @@ def check(name: str, cond: bool) -> None:
fp2 = m.build_fingerprint(prov)
check(f"{label}: deterministic token", fp["token"] == fp2["token"])

# --- the effective execution gate must participate in the fingerprint
runtime_kwargs = {
"input_tf": "15",
"script_tf": "",
"bar_magnifier": False,
"magnifier_samples": 4,
"magnifier_distribution": "ENDPOINTS",
"chart_timezone": "UTC",
}
gate_ms = 1712345678901
open_runtime = rs.build_runtime_provenance(runtime_kwargs, None)
gated_runtime = rs.build_runtime_provenance(runtime_kwargs, gate_ms)
same_gate_runtime = rs.build_runtime_provenance(runtime_kwargs, str(gate_ms))
check("run_strategy: open execution records trade_start_ms=null",
"trade_start_ms" in open_runtime and open_runtime["trade_start_ms"] is None)
check("run_strategy: gated execution records trade_start_ms as int",
gated_runtime["trade_start_ms"] == gate_ms
and isinstance(gated_runtime["trade_start_ms"], int))
check("run_strategy: equivalent gate values normalize identically",
same_gate_runtime == gated_runtime)
check("run_strategy: runtime helper is pure",
"trade_start_ms" not in runtime_kwargs)

open_fp = rs.build_fingerprint({"runtime": open_runtime})
gated_fp = rs.build_fingerprint({"runtime": gated_runtime})
same_gate_fp = rs.build_fingerprint({"runtime": same_gate_runtime})
check("run_strategy: gated/open digests differ",
gated_fp["digest"] != open_fp["digest"])
check("run_strategy: same effective gate has deterministic digest",
gated_fp["digest"] == same_gate_fp["digest"])

source_tree = ast.parse(RUN_STRATEGY.read_text(encoding="utf-8"))
main_fn = next((node for node in source_tree.body
if isinstance(node, ast.FunctionDef) and node.name == "main"), None)
main_uses_helper = main_fn is not None and any(
isinstance(node, ast.Call)
and isinstance(node.func, ast.Name)
and node.func.id == "build_runtime_provenance"
for node in ast.walk(main_fn)
)
check("run_strategy: main fingerprint path uses runtime helper",
main_uses_helper)

# --- the two copies must agree byte-for-byte on the fixture ----------
check("copies agree: parse_strategy_params",
rj.parse_strategy_params(FIXTURE_CPP) == rs.parse_strategy_params(FIXTURE_CPP))
Expand Down
22 changes: 14 additions & 8 deletions scripts/run_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,19 @@ def build_fingerprint(provenance: dict) -> dict:
# <<< fingerprint helpers


def build_runtime_provenance(run_kwargs: dict, trade_start_ms: int | None) -> dict:
"""Return the effective runtime settings that participate in a fingerprint."""
return {
"input_tf": run_kwargs.get("input_tf") or "",
"script_tf": run_kwargs.get("script_tf") or "",
"bar_magnifier": bool(run_kwargs.get("bar_magnifier")),
"magnifier_samples": int(run_kwargs.get("magnifier_samples") or 4),
"magnifier_distribution": run_kwargs.get("magnifier_distribution") or "ENDPOINTS",
"chart_timezone": run_kwargs.get("chart_timezone") or "",
"trade_start_ms": None if trade_start_ms is None else int(trade_start_ms),
}


# --- ctypes mirror of <pineforge/pineforge.h> -------------------------
#
# Field order, types, and widths must match the C struct exactly. The
Expand Down Expand Up @@ -1325,14 +1338,7 @@ def main() -> int:
overrides_applied = {
str(k): str(v) for k, v in (run_kwargs.get("strategy_overrides") or {}).items()
}
runtime = {
"input_tf": run_kwargs.get("input_tf") or "",
"script_tf": run_kwargs.get("script_tf") or "",
"bar_magnifier": bool(run_kwargs.get("bar_magnifier")),
"magnifier_samples": int(run_kwargs.get("magnifier_samples") or 4),
"magnifier_distribution": run_kwargs.get("magnifier_distribution") or "ENDPOINTS",
"chart_timezone": run_kwargs.get("chart_timezone") or "",
}
runtime = build_runtime_provenance(run_kwargs, trade_start_ms)
fp = build_fingerprint(build_provenance(
engine_version(strat.lib),
cpp_path if cpp_path.exists() else None,
Expand Down
6 changes: 6 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ add_test(
${PROJECT_SOURCE_DIR}/scripts/derive_corpus_feeds_self_test.py
)

add_test(
NAME test_fingerprint
COMMAND ${Python3_EXECUTABLE}
${PROJECT_SOURCE_DIR}/scripts/fingerprint_self_test.py
)

# When PINEFORGE_ENABLE_COVERAGE is ON we also instrument the test
# binaries — header-only code (Series, na, color, log, math::pine_random)
# is otherwise reported as 0% because it's only inlined into test TUs.
Expand Down
Loading