evaluate_reconciliation_gate + evaluate_gate now take a ResolvedPolicy (resolve_policy(POLICY, gate_spec)); the module constants (MIN_FORWARD_DAYS / RECON_* ) + gate.py inline defaults are removed — thresholds now come from the single GatePolicy. Verdict logic + reason strings unchanged → byte-identical verdicts (full suite 1873 green proves it). _gate_verdict resolves once. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
34 lines
1.2 KiB
Python
34 lines
1.2 KiB
Python
"""Phase-1 go/no-go gate: WAIT until min_days, then GO if forward return+Sharpe clear the spec, else NO_GO."""
|
|
from __future__ import annotations
|
|
|
|
from fxhnt.application.forward_models import ForwardSummary
|
|
from fxhnt.application.gate import evaluate_gate
|
|
from fxhnt.application.gate_policy import POLICY
|
|
from fxhnt.application.gate_policy_resolve import resolve_policy
|
|
|
|
_SPEC = {"min_days": 20, "min_total_return": 0.0, "min_sharpe": 0.5}
|
|
_POLICY = resolve_policy(POLICY, _SPEC)
|
|
|
|
|
|
def _sm(days, total_return, sharpe):
|
|
return ForwardSummary("x", "2026-06-12", days, 1.0 + total_return, total_return, sharpe, -0.05)
|
|
|
|
|
|
def test_wait_before_min_days() -> None:
|
|
assert evaluate_gate(_sm(7, 0.05, 3.0), _POLICY).status == "WAIT"
|
|
|
|
|
|
def test_go_when_thresholds_cleared() -> None:
|
|
v = evaluate_gate(_sm(25, 0.04, 1.2), _POLICY)
|
|
assert v.status == "GO"
|
|
|
|
|
|
def test_no_go_when_return_negative_after_window() -> None:
|
|
v = evaluate_gate(_sm(25, -0.02, 1.2), _POLICY)
|
|
assert v.status == "NO_GO" and "return" in v.reason.lower()
|
|
|
|
|
|
def test_no_go_when_sharpe_below_floor() -> None:
|
|
v = evaluate_gate(_sm(25, 0.04, 0.1), _POLICY)
|
|
assert v.status == "NO_GO" and "sharpe" in v.reason.lower()
|