# Gate 3 Results Validation **Purpose:** Verify shadow run results meet all validation gates **Usage:** After shadow run execution completes (status = EvaluationComplete) --- ## Validation Gates Overview | Gate | Threshold | JSON Field | Expected | |------|-----------|-----------|----------| | **PBO** | ≤ 20% | `pbo_under_20` | `true` | | **DSR** | ≥ 95th | `dsr_above_95` | `true` | | **Cost 2x** | Positive | `cost_2x_positive` | `true` | | **All Passed** | 3/3 gates | `all_gates_passed` | `true` | --- ## Step 1: Check Overall Status **SQL Query:** ```sql SELECT run_id, status, CAST(validation_gates_json->>'all_gates_passed' AS bool) as gates_passed, validation_gates_json::text as full_gates, published_at FROM model_operations.shadow_run ORDER BY published_at DESC LIMIT 1; ``` **Expected Result:** ``` run_id | status | gates_passed | full_gates | published_at b2c3d4e5... | EvaluationComplete | true | {"all_gates_passed":true, ...} | 2026-08-02 14:30:45 ``` **Interpretation:** - ✅ **Status = EvaluationComplete**: Run finished successfully - ✅ **gates_passed = true**: All validation gates passed - ⚠️ **Status = Failed**: Check error_message column for failure reason - ⚠️ **gates_passed = false**: At least one gate failed (see details below) --- ## Step 2: Validate Each Gate ### Gate 2a: PBO (Probability of Backtest Overfit) ≤ 20% **SQL Query:** ```sql SELECT CAST(validation_gates_json->>'pbo' AS numeric) as pbo_value, CAST(validation_gates_json->>'pbo_under_20' AS bool) as pbo_pass FROM model_operations.shadow_run ORDER BY published_at DESC LIMIT 1; ``` **Expected Result:** ``` pbo_value | pbo_pass 0.15 | true ``` **Interpretation:** - ✅ **pbo_value ≤ 0.20**: Strategy not overfit to historical data - ❌ **pbo_value > 0.20**: Strategy may be overfit; consider: - Different date range - Different model parameters - Simpler strategy **Action if Failed:** ``` Risk Level: HIGH Recommendation: Review strategy assumptions, try longer backtest period Contact: Risk committee for decision on proceeding despite failed gate ``` --- ### Gate 2b: DSR (Daily Sharpe Ratio) ≥ 95th Percentile **SQL Query:** ```sql SELECT CAST(validation_gates_json->>'dsr' AS numeric) as dsr_value, CAST(validation_gates_json->>'dsr_above_95' AS bool) as dsr_pass, CAST(validation_gates_json->>'sharpe' AS numeric) as sharpe_ratio FROM model_operations.shadow_run ORDER BY published_at DESC LIMIT 1; ``` **Expected Result:** ``` dsr_value | dsr_pass | sharpe_ratio 0.96 | true | 1.45 ``` **Interpretation:** - ✅ **dsr_value ≥ 0.95**: Daily Sharpe ratio above 95th percentile (robust) - ✅ **sharpe_ratio ≥ 1.0**: Standard Sharpe ratio is positive - ❌ **dsr_value < 0.95**: Inconsistent daily performance - ❌ **sharpe_ratio < 1.0**: Weak risk-adjusted returns **Action if Failed:** ``` Risk Level: MEDIUM Recommendation: Analyze volatility patterns, check for asymmetric risk Contact: Quant team for robustness review ``` --- ### Gate 2c: Cost 2x (Returns Survive Doubled Fees) **SQL Query:** ```sql SELECT CAST(validation_gates_json->>'cost_2x_positive' AS bool) as cost_pass, CAST(validation_gates_json->>'returns' AS numeric) as total_return, (validation_gates_json->'cost_analysis_json'->>'doubled_fee_return') as cost_2x_return FROM model_operations.shadow_run ORDER BY published_at DESC LIMIT 1; ``` **Expected Result:** ``` cost_pass | total_return | cost_2x_return true | 0.28 | 0.18 ``` **Interpretation:** - ✅ **cost_pass = true**: Returns remain positive even with 2x fees - ✅ **cost_2x_return > 0**: Robust to fee increases - ❌ **cost_pass = false**: Strategy margin eroded by fees **Action if Failed:** ``` Risk Level: MEDIUM Recommendation: Review trading costs, optimize execution Contact: Trading desk for fee negotiations ``` --- ## Step 3: Phase Analysis (Optional but Recommended) **SQL Query:** ```sql SELECT (phase_analysis_json->'bull'->>'sharpe')::numeric as bull_sharpe, (phase_analysis_json->'bull'->>'return')::numeric as bull_return, (phase_analysis_json->'bear'->>'sharpe')::numeric as bear_sharpe, (phase_analysis_json->'bear'->>'return')::numeric as bear_return, (phase_analysis_json->'sideways'->>'sharpe')::numeric as sideways_sharpe, (phase_analysis_json->'sideways'->>'return')::numeric as sideways_return FROM model_operations.shadow_run ORDER BY published_at DESC LIMIT 1; ``` **Expected Result:** ``` bull_sharpe | bull_return | bear_sharpe | bear_return | sideways_sharpe | sideways_return 1.8 | 0.35 | 0.9 | 0.15 | 1.2 | 0.22 ``` **Interpretation:** - ✅ **All non-zero**: Strategy works across market regimes - ✅ **Bull sharpe > bear sharpe**: Better in trending markets (typical) - ⚠️ **Bear sharpe < 1.0**: Struggles in downturns (acceptable) - ❌ **Any = 0**: Missing data for market phase **Insights:** - Bull regime: +35% return (1.8 Sharpe) — strong upside capture - Bear regime: +15% return (0.9 Sharpe) — downside protection working - Sideways: +22% return (1.2 Sharpe) — range-bound trading effective --- ## Step 4: Audit Trail Verification **SQL Query:** ```sql SELECT sr.run_id, sr.published_at, COUNT(DISTINCT om.correlation_id) as distinct_correlation_ids, COUNT(DISTINCT im.consumer_id) as consumers_processed, (SELECT COUNT(*) FROM model_operations.approval_queue WHERE run_id = sr.run_id) as approval_records FROM model_operations.shadow_run sr LEFT JOIN building_blocks.outbox_message om ON sr.run_id::text = om.payload_json->>'runId' LEFT JOIN outbox.inbox im ON om.message_id = im.outbox_id WHERE sr.run_id = '' GROUP BY sr.run_id, sr.published_at; ``` **Expected Result:** ``` run_id | published_at | distinct_correlation_ids | consumers_processed | approval_records b2c3d4e5... | 2026-08-02 14:30:45 | 1 | 3 | 1 ``` **Interpretation:** - ✅ **distinct_correlation_ids = 1**: Single run traced end-to-end - ✅ **consumers_processed ≥ 1**: Events routed to consumers - ✅ **approval_records = 1**: Approval auto-populated - ❌ **Any = 0**: Audit trail incomplete --- ## Summary Checklist After execution, verify: - [ ] Status = EvaluationComplete - [ ] all_gates_passed = true - [ ] pbo_under_20 = true (PBO ≤ 20%) - [ ] dsr_above_95 = true (DSR ≥ 95th) - [ ] cost_2x_positive = true (2x fee robust) - [ ] Phase analysis populated (bull, bear, sideways) - [ ] Approval queue auto-populated (status = Pending) - [ ] Correlation IDs in audit trail - [ ] No error_message in shadow_run --- ## Decision Points | Scenario | Action | |----------|--------| | All gates ✅ | Proceed to approval workflow (Gate 4) | | PBO fails | Contact Risk committee | | DSR fails | Contact Quant team for robustness review | | Cost gate fails | Discuss with Trading desk | | Audit trail incomplete | Investigate Outbox→Inbox pipeline | | Approval not auto-populated | Check downstream consumer job logs | --- ## Next Steps (if all validated) 1. **Query Approval Queue** ```sql SELECT id, run_id, status, requested_at FROM model_operations.approval_queue WHERE run_id = ''; ``` 2. **Maker-Checker Approval** ```bash curl -X POST http://localhost:5000/api/v1/approval-queue/{id}/approve \ -H "Authorization: Bearer " \ -d '{"approvalReason":"All gates passed, approved for activation"}' ``` 3. **Verify Approval Updated** ```sql SELECT status, approved_by, approval_reason, approved_at FROM model_operations.approval_queue WHERE run_id = ''; ``` --- ## Troubleshooting **Problem: Shadow run missing validation gates JSON** ``` Solution: Check error_message for execution errors. Re-run with logs enabled. ``` **Problem: Approval not auto-created** ``` Solution: Check DownstreamConsumerJob logs. Verify ShadowRunCompletedEvent was emitted. ``` **Problem: One gate failed (e.g., PBO > 20%)** ``` Solution: This is NOT a blocker for activation, but flags increased backtest risk. Review with Risk committee before activation. ``` **Problem: Phase analysis all zeros** ``` Solution: Check date range covered all market regimes. If short period, results are expected. Use longer window for production. ```