From 13ccce5238fb6dbffc077086da07222cb6dddf2c Mon Sep 17 00:00:00 2001 From: vijay sharma Date: Tue, 27 Jan 2026 19:40:59 +0100 Subject: [PATCH] cxc --- adapters/ccxt_shim/breeze_ccxt.py | 1 + scripts/accept_all.sh | 3 ++ scripts/gates/p17_degraded_mode.sh | 24 +++++++++ scripts/gates/p17_invalid_symbol.sh | 18 +++++++ scripts/gates/p17_rate_limit.sh | 27 ++++++++++ .../test_icicibreeze_invalid_symbol.py | 50 +++++++++++++++++++ 6 files changed, 123 insertions(+) create mode 100644 scripts/gates/p17_degraded_mode.sh create mode 100644 scripts/gates/p17_invalid_symbol.sh create mode 100644 scripts/gates/p17_rate_limit.sh create mode 100644 tests/exchange/test_icicibreeze_invalid_symbol.py diff --git a/adapters/ccxt_shim/breeze_ccxt.py b/adapters/ccxt_shim/breeze_ccxt.py index fa68a6039..92ed4ebda 100644 --- a/adapters/ccxt_shim/breeze_ccxt.py +++ b/adapters/ccxt_shim/breeze_ccxt.py @@ -66,6 +66,7 @@ class BreezeCCXT(ccxt.Exchange): # Rate Limiting # P17: Switched to centralized RateLimiter with Env support self.rate_limiter = RateLimiter() + self._security_master_cache: dict[str, Any] | None = None self.market_hours = MarketHoursGuard() self.degraded_guard = DegradedModeGuard() self.order_router = OrderRouter(lambda: self.markets) diff --git a/scripts/accept_all.sh b/scripts/accept_all.sh index 4e199f3ea..f38f235be 100755 --- a/scripts/accept_all.sh +++ b/scripts/accept_all.sh @@ -32,6 +32,9 @@ ALL_GATES=( "p14_market_hours" "p15_risk_guardrails" "p16_order_router" + "p17_rate_limit" + "p17_degraded_mode" + "p17_invalid_symbol" ) # Parse flags diff --git a/scripts/gates/p17_degraded_mode.sh b/scripts/gates/p17_degraded_mode.sh new file mode 100644 index 000000000..cbb06a39b --- /dev/null +++ b/scripts/gates/p17_degraded_mode.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -e +source scripts/gates/common.sh + +# P17 Degraded Mode Gate +# Verifies degraded mode blocking. + +echo "==========================================================" +echo "GATE: P17 Degraded Mode" +echo "==========================================================" + +echo "1. Verify Forced Degraded Mode (Integration)" +# Uses tests/exchange/test_icicibreeze_integration_degraded.py +# Matches "degraded_block" +pytest -v tests/exchange/test_icicibreeze_integration_degraded.py +echo " [+] Integration Verified" + +echo "2. Verify Logic (Unit Tests)" +pytest -v tests/test_degraded_mode_force_block.py tests/test_degraded_mode_auto_trigger.py +echo " [+] Logic Verified" + +echo "----------------------------------------------------------" +echo "GATE P17-DegradedMode PASSED" +echo "----------------------------------------------------------" diff --git a/scripts/gates/p17_invalid_symbol.sh b/scripts/gates/p17_invalid_symbol.sh new file mode 100644 index 000000000..16ab79f03 --- /dev/null +++ b/scripts/gates/p17_invalid_symbol.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e +source scripts/gates/common.sh + +# P17 Invalid Symbol Resilience Gate +# Verifies system handles bad data gracefully. + +echo "==========================================================" +echo "GATE: P17 Invalid Symbol" +echo "==========================================================" + +echo "1. Verify Invalid Symbol Handling (Pytest)" +pytest -v tests/exchange/test_icicibreeze_invalid_symbol.py +echo " [+] Resilience Verified" + +echo "----------------------------------------------------------" +echo "GATE P17-InvalidSymbol PASSED" +echo "----------------------------------------------------------" diff --git a/scripts/gates/p17_rate_limit.sh b/scripts/gates/p17_rate_limit.sh new file mode 100644 index 000000000..3e4d67556 --- /dev/null +++ b/scripts/gates/p17_rate_limit.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -e +source scripts/gates/common.sh + +# P17 Rate Limit Gate +# Verifies that shim enforces rate limits in both sleep (default) and block modes. + +echo "==========================================================" +echo "GATE: P17 Rate Limiter" +echo "==========================================================" + +echo "1. Verify Block Mode (Pytest Integration w/ Env)" +# Uses tests/exchange/test_icicibreeze_rate_limit_applied.py +# Env vars set in the test fixture, but we can double check logic here if needed? +# Actually the test file is self-contained. Is that enough? +# Plan says: "Run pytest tests with specific failure expectations" +# Our integration test file forces block mode. +pytest -v tests/exchange/test_icicibreeze_rate_limit_applied.py +echo " [+] Block Mode Verified" + +echo "2. Verify Sleep Mode (Unit Test)" +pytest -v tests/test_rate_limiter_sleep_mode.py +echo " [+] Sleep Mode Verified" + +echo "----------------------------------------------------------" +echo "GATE P17-RateLimit PASSED" +echo "----------------------------------------------------------" diff --git a/tests/exchange/test_icicibreeze_invalid_symbol.py b/tests/exchange/test_icicibreeze_invalid_symbol.py new file mode 100644 index 000000000..243ced577 --- /dev/null +++ b/tests/exchange/test_icicibreeze_invalid_symbol.py @@ -0,0 +1,50 @@ +import pytest +import os +from unittest import mock +from freqtrade.exceptions import OperationalException +from adapters.ccxt_shim.breeze_ccxt import BreezeCCXT + + +@pytest.fixture +def exchange_for_resilience(): + exchange = BreezeCCXT() + exchange.breeze = mock.Mock() # Mock session + return exchange + + +def test_fetch_ohlcv_invalid_symbol_raises_cleanly(exchange_for_resilience): + # Mock _load_security_master to return empty master, so any symbol is not found + with mock.patch.object( + exchange_for_resilience, + "_load_security_master", + return_value={"nfo": {}, "nse": {"by_symbol": {}}}, + ): + # "INVALID/INR" -> Underlying=INVALID, Quote=INR -> CASH + # Lookup INVALID in nse master -> None -> Raise "Cash symbol not found" + with pytest.raises(OperationalException, match="Cash symbol not found"): + exchange_for_resilience.fetch_ohlcv("INVALID/INR", "5m") + + +def test_fetch_ohlcv_api_error_returns_empty(exchange_for_resilience): + # Case 2: Breeze SDK returns Generic Exception + symbol = "RELIANCE/INR" + + # Mock master to find symbol + mock_master = {"nse": {"by_symbol": {"RELIANCE": {"token": "123"}}}, "nfo": {}} + + with mock.patch.object( + exchange_for_resilience, "_load_security_master", return_value=mock_master + ): + with mock.patch.object( + exchange_for_resilience, + "_parse_symbol", + return_value={"stock_code": "REL", "exchange_code": "NSE", "product_type": "cash"}, + ): + # Mock Breeze SDK to raise Exception + exchange_for_resilience.breeze.get_historical_data_v2.side_effect = Exception( + "API Error" + ) + + # Should NOT raise, but return empty list (resilience) + res = exchange_for_resilience.fetch_ohlcv(symbol, "5m") + assert res == []