From 75879aebafad4e7d18e6e201cd7538ee813c92c0 Mon Sep 17 00:00:00 2001 From: vijay sharma Date: Sat, 24 Jan 2026 13:59:30 +0100 Subject: [PATCH] feat: Add P09x acceptance gate script and enhance pair generation tools with argument aliases and explicit security master path for determinism verification. --- scripts/accept_p09x.sh | 81 +++++++++++++++++++++ scripts/make_config_with_pairs.py | 4 +- scripts/universe_scan_and_generate_pairs.py | 30 ++++++-- 3 files changed, 106 insertions(+), 9 deletions(-) create mode 100755 scripts/accept_p09x.sh diff --git a/scripts/accept_p09x.sh b/scripts/accept_p09x.sh new file mode 100755 index 000000000..b141befc6 --- /dev/null +++ b/scripts/accept_p09x.sh @@ -0,0 +1,81 @@ +#!/bin/bash +set -e + +# P09 Acceptance Gate Script +# Objective: Verify determinism of Universe Scan and Whitelist Generation + +STRATEGY_YAML="user_data/india_strategy.yaml" +BASE_CONFIG="user_data/config_icicibreeze.json" +OUT_DIR="user_data/generated" +mkdir -p "$OUT_DIR" + +UNIVERSE_SCAN_PY="scripts/universe_scan_and_generate_pairs.py" +GEN_WHITELIST_PY="scripts/make_config_with_pairs.py" +SECURITY_MASTER_TXT="user_data/data/icicibreeze/FONSEScripMaster.txt" + +V1_PAIRS="$OUT_DIR/p09x_pairs_v1.json" +V1_REPORT="$OUT_DIR/p09x_report_v1.json" +V1_CONFIG="$OUT_DIR/config_p09x_v1.json" + +V2_PAIRS="$OUT_DIR/p09x_pairs_v2.json" +V2_REPORT="$OUT_DIR/p09x_report_v2.json" +V2_CONFIG="$OUT_DIR/config_p09x_v2.json" + +PYTHON=".venv/bin/python3" + +echo "=== STEP 1: Run Universe Scan (Pass 1) ===" +$PYTHON "$UNIVERSE_SCAN_PY" \ + --config "$STRATEGY_YAML" \ + --security-master "$SECURITY_MASTER_TXT" \ + --out-pairs "$V1_PAIRS" \ + --out-report "$V1_REPORT" + +echo "=== STEP 2: Generate Config (Pass 1) ===" +$PYTHON "$GEN_WHITELIST_PY" \ + --base-config "$BASE_CONFIG" \ + --pairs "$V1_PAIRS" \ + --out-config "$V1_CONFIG" + +echo "=== STEP 3: Run Universe Scan (Pass 2) ===" +$PYTHON "$UNIVERSE_SCAN_PY" \ + --config "$STRATEGY_YAML" \ + --security-master "$SECURITY_MASTER_TXT" \ + --out-pairs "$V2_PAIRS" \ + --out-report "$V2_REPORT" + +echo "=== STEP 4: Generate Config (Pass 2) ===" +$PYTHON "$GEN_WHITELIST_PY" \ + --base-config "$BASE_CONFIG" \ + --pairs "$V2_PAIRS" \ + --out-config "$V2_CONFIG" + +echo "=== STEP 5: Verify Determinism ===" +sha256sum "$V1_PAIRS" "$V2_PAIRS" +sha256sum "$V1_REPORT" "$V2_REPORT" +sha256sum "$V1_CONFIG" "$V2_CONFIG" + +# Compare hashes and fail if they differ +DIFF=$(sha256sum "$V1_PAIRS" "$V2_PAIRS" | awk '{print $1}' | sort | uniq | wc -l) +if [ "$DIFF" -ne 1 ]; then + echo "ERROR: Determinism check failed for pairs!" + exit 1 +fi + +echo "=== STEP 6: Verify Content Integrity ===" +# Check if RELIANCE (from stocks) and NIFTY (from indices) are present if they were in yaml +grep -q "RELIANCE" "$V1_PAIRS" || echo "Note: RELIANCE not in pairs (expected if filter excluded it)" +grep -q "NIFTY" "$V1_PAIRS" || echo "Note: NIFTY not in pairs (expected if filter excluded it)" + +echo "=== STEP 7: Check Empty Cases ===" +if [ ! -s "$V1_PAIRS" ]; then + echo "ERROR: Generated pairs file is empty!" + exit 1 +fi + +echo "=== STEP 8: Archive Results ===" +tar -czf "$OUT_DIR/p09x_acceptance_artifacts.tar.gz" \ + "$V1_PAIRS" "$V1_REPORT" "$V1_CONFIG" \ + "$V2_PAIRS" "$V2_REPORT" "$V2_CONFIG" + +echo "=== P09 ACCEPTANCE GATE PASSED ===" +echo "Artifacts: $OUT_DIR/p09x_acceptance_artifacts.tar.gz" diff --git a/scripts/make_config_with_pairs.py b/scripts/make_config_with_pairs.py index 2d60b926b..ab5967b23 100644 --- a/scripts/make_config_with_pairs.py +++ b/scripts/make_config_with_pairs.py @@ -32,9 +32,9 @@ def _write_json(path: Path, payload: dict) -> None: def _parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Create config with generated pairs whitelist.") - parser.add_argument("--base", required=True, help="Base config JSON path") + parser.add_argument("--base", "--base-config", required=True, help="Base config JSON path") parser.add_argument("--pairs", required=True, help="Pairs JSON path") - parser.add_argument("--out", required=True, help="Output config path") + parser.add_argument("--out", "--out-config", required=True, help="Output config path") return parser.parse_args() diff --git a/scripts/universe_scan_and_generate_pairs.py b/scripts/universe_scan_and_generate_pairs.py index 5ce1b1f59..19284626c 100644 --- a/scripts/universe_scan_and_generate_pairs.py +++ b/scripts/universe_scan_and_generate_pairs.py @@ -128,11 +128,15 @@ def _parse_option_policy(payload: dict[str, Any]) -> OptionPolicy: ) -def _load_contracts() -> SecurityMaster: - master_file = find_latest_master_file("FONSEScripMaster.txt") - if not master_file: - logger.error("SecurityMaster file not found.") - raise FileNotFoundError("FONSEScripMaster.txt not found") +def _load_contracts(master_path: Path | None = None) -> SecurityMaster: + if master_path: + master_file = master_path + else: + master_file = find_latest_master_file("FONSEScripMaster.txt") + + if not master_file or not master_file.exists(): + logger.error("SecurityMaster file not found: %s", master_file) + raise FileNotFoundError(f"Security master file not found: {master_file}") master = load_nfo_options_master(master_file) return SecurityMaster(master.get("by_contract", {})) @@ -175,15 +179,27 @@ def _parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Scan universe and generate pairs.") parser.add_argument( "--strategy-config", + "--config", required=True, help="Strategy YAML config path", ) - parser.add_argument("--out", required=True, help="Output pairs JSON path") + parser.add_argument( + "--out", + "--out-pairs", + required=True, + help="Output pairs JSON path", + ) parser.add_argument( "--report", + "--out-report", default=None, help="Output report JSON path (default: derived from --out)", ) + parser.add_argument( + "--security-master", + default=None, + help="Path to FONSEScripMaster.txt", + ) parser.add_argument( "--mode", choices=["mock", "real"], @@ -331,7 +347,7 @@ def main() -> None: universe = _parse_universe_config(payload) option_policy = _parse_option_policy(payload) - security_master = _load_contracts() + security_master = _load_contracts(Path(args.security_master) if args.security_master else None) today = _kolkata_today() pairs, report = _build_pairs_report(universe, option_policy, security_master, today)