diff --git a/scripts/accept_all.sh b/scripts/accept_all.sh index 5193a78b1..203bdde13 100755 --- a/scripts/accept_all.sh +++ b/scripts/accept_all.sh @@ -105,7 +105,10 @@ for gate in "${GATES[@]}"; do # P22 and P25 require BOTH modes for full verification as per "Delta Harden" req MODES_TO_RUN=("$MODE") if [[ "$MODE" == "pos" ]]; then - if [[ "$gate" == "p22_real_mode_market_data" ]] || [[ "$gate" == "p25_security_master_refresh" ]]; then + if [[ "$gate" == "p22_real_mode_market_data" ]] || \ + [[ "$gate" == "p25_security_master_refresh" ]] || \ + [[ "$gate" == "p26_indicator_governance" ]] || \ + [[ "$gate" == "p27_smart_money" ]]; then MODES_TO_RUN=("pos" "neg") echo ">>> Info: Automatically scheduling Neg mode for hardened gate: $gate" fi diff --git a/scripts/gates/p26_indicator_governance.sh b/scripts/gates/p26_indicator_governance.sh index 3c7276156..b9ca2bb2a 100644 --- a/scripts/gates/p26_indicator_governance.sh +++ b/scripts/gates/p26_indicator_governance.sh @@ -62,22 +62,20 @@ EOF elif [ "$GATE_MODE" == "neg" ]; then echo ">>> Gate P26: Negative (Lookahead Violation)..." - # Run the specific negative test case expecting failure - # We run pytest targeting the violation test + # Run the Negative Test which is DESIGNED TO FAIL + # If the production guard works, pytest will exit with code 1. - if pytest tests/strategy/test_p26_guards.py::test_no_lookahead_sanity_violation > "$ARTIFACT_DIR/neg_test.log" 2>&1; then - # This test is DESIGNED to pass if it raises ValueError (pytest handles it) - # Wait, if I want to prove the GUARD works, the UNIT TEST should PASS (by asserting validation). - # Ah, the requirement says: "run with a fixture that intentionally violates... assert failure occurs" - # Since I wrote the test to "expect" ValueError using pytest.raises, the test itself PASSES. - # This confirms the guard IS raising the error. - - echo "[OK] Guard corrected raised ValueError on lookahead violation." - echo "P26_NEG_EXPECTED_FAIL" # Misnomer? It's success of the negative control. - # "gate exits 0 when expected failure is observed" -> logic matches. + set +e + pytest tests/strategy/test_p26_guards_negative.py > "$ARTIFACT_DIR/neg_test.log" 2>&1 + PYTEST_EXIT=$? + set -e + + if [ $PYTEST_EXIT -ne 0 ]; then + echo "[OK] Negative test failed as expected (Guard active)." + echo "P26_NEG_EXPECTED_FAIL" finish_gate 0 else - echo "[FAIL] Negative test failed (did not raise expected error?)" + echo "[FAIL] Negative test PASSED! (Guard failed to raise error?)" cat "$ARTIFACT_DIR/neg_test.log" finish_gate 1 fi diff --git a/scripts/gates/p27_smart_money.sh b/scripts/gates/p27_smart_money.sh index d5fd50372..c2956d45c 100644 --- a/scripts/gates/p27_smart_money.sh +++ b/scripts/gates/p27_smart_money.sh @@ -24,19 +24,21 @@ if [ "$GATE_MODE" == "pos" ]; then elif [ "$GATE_MODE" == "neg" ]; then echo ">>> Gate P27: Negative (Logic Rejection)..." - # We want to verify that a "Bad Snapshot" blocks trade. - # We already have a unit test `test_snapshot_bad_low_oi` which asserts correct rejection. - # So if that test PASSES, it means rejection IS happening correctly. - # The requirement "gate exits 0 when expected failure observed" is satisfied if pytest passes, - # because pytest asserts "allow_trade is False". + # Run the Negative Test which is DESIGNED TO FAIL + # Logic returns False, Assert True => Test Fails - # So we just run specific test filter - if pytest -v tests/strategy/test_p27_smart_money_fr203.py -k test_snapshot_bad_low_oi; then - echo "[OK] Smart Money Logic correctly rejected bad snapshot." - echo "P27_NEG_EXPECTED_FAIL" # Mapped to semantic meaning "Rejection Confirmed" + set +e + pytest tests/strategy/test_p27_negative_expected_fail.py > "$ARTIFACT_DIR/neg_test.log" 2>&1 + PYTEST_EXIT=$? + set -e + + if [ $PYTEST_EXIT -ne 0 ]; then + echo "[OK] Negative test failed as expected (Logic correctly rejected trade)." + echo "P27_NEG_EXPECTED_FAIL" finish_gate 0 else - echo "[FAIL] Negative test verification failed." + echo "[FAIL] Negative test PASSED! (Logic incorrectly allowed trade?)" + cat "$ARTIFACT_DIR/neg_test.log" finish_gate 1 fi diff --git a/tests/strategy/test_p26_guards_negative.py b/tests/strategy/test_p26_guards_negative.py new file mode 100644 index 000000000..f380f74f0 --- /dev/null +++ b/tests/strategy/test_p26_guards_negative.py @@ -0,0 +1,27 @@ +""" +P26 Negative Test +This test is intended to FAIL. +It attempts to assert that a Lookahead Violation does NOT raise an error. +Since the production code (guards.py) correctly raises ValueError, this test will fail. +The Acceptance Gate will observe this failure to confirm the guard is active. +""" + +import pytest +import pandas as pd +import numpy as np +from user_data.strategies.guards import no_lookahead_sanity + + +def test_negative_control_lookahead_should_fail(): + # Construct a dataframe with a clear lookahead violation (NaN in last row) + df = pd.DataFrame({"rsi": [50, 55, np.nan]}) + + # We assert that this call succeeds (no exception raised). + # THIS ASSERTION IS WRONG BY DESIGN. + # The production code WILL raise ValueError, causing this test to FAIL. + try: + no_lookahead_sanity(df, ["rsi"]) + except ValueError: + pytest.fail( + "Guard raised ValueError as expected, but this test is designed to catch that as a FAILURE of the assertion 'no error raised'." + ) diff --git a/tests/strategy/test_p27_negative_expected_fail.py b/tests/strategy/test_p27_negative_expected_fail.py new file mode 100644 index 000000000..33c6332c0 --- /dev/null +++ b/tests/strategy/test_p27_negative_expected_fail.py @@ -0,0 +1,40 @@ +""" +P27 Negative Test +This test is intended to FAIL. +It attempts to assert that a BAD snapshot (Low OI) is ALLOWED. +Since the production logic (SmartMoneyEngine) correctly sets allow_trade=False, this assertion fails. +The Acceptance Gate will observe this failure. +""" + +import pytest +from user_data.strategies.smart_money_fr203 import SmartMoneyEngine, OptionChainSnapshot, StrikeRow + + +def make_snapshot(oi_pct=5.0) -> OptionChainSnapshot: + # Low OI (5% < 10% Required) + return OptionChainSnapshot( + underlying="NIFTY", + ts_utc="2025-01-01T00:00:00Z", + strikes=[ + StrikeRow( + strike=20000, + right="CE", + oi_change_pct=oi_pct, + volume=150000, + ltp_change_pct=2.0, + iv_change_pct=0.1, + ), + ], + ) + + +def test_negative_control_bad_snapshot_should_fail(): + snap = make_snapshot(oi_pct=5.0) + decision = SmartMoneyEngine.evaluate(snap) + + # We assert that trade IS allowed. + # THIS ASSERTION IS WRONG BY DESIGN. + # The production logic will return False, causing this assertion to fail (pytest exit code 1). + assert decision.allow_trade is True, ( + "TEST FAILED: Logic correctly rejected the trade (allow_trade=False)" + )