Story 2.2: Implement WARNING Triggers

Story ID: STORY-2.2
Epic: EPIC-002 (Phase 2 - Complete Grid Exit Strategy)
Priority: P0
Effort: 4-6 hours (Actual: ~2 hours)
Status: review


Story

Implement WARNING state trigger logic requiring 2+ conditions to fire (not single condition). This prevents false alarms from single noisy indicators.

WARNING Conditions (require 2+ to trigger):

  1. TRANSITION probability ≥ 40% (configurable)
  2. Regime confidence declining over 3 bars
  3. Efficiency Ratio rising above 0.6 (configurable)
  4. Mean reversion speed slowing
  5. Volatility expansion 1.1-1.25×

Acceptance Criteria

  • evaluate_warning_conditions() function implemented in src/exit_strategy/triggers/warning.py
  • Requires 2+ conditions to trigger WARNING (NOT single condition)
  • All 5 condition checks implemented
  • Configurable thresholds via YAML (parameters accepted, schema in Story 2.6)
  • Unit tests cover edge cases (0, 1, 2, 5 conditions met)
  • Returns (ExitState, List[str]) with reasons for conditions met

Tasks/Subtasks

Task 1: Create WARNING triggers module ✅

  • Create src/exit_strategy/triggers/warning.py
  • Import ExitState enum from evaluator (already exists)
  • Import necessary types and dependencies

Task 2: Implement condition check functions ✅

  • Implement check_transition_probability() - TRANSITION prob ≥ threshold
  • Implement check_confidence_declining() - regime confidence declining over 3 bars
  • Implement check_efficiency_ratio_rising() - ER rising above threshold
  • Implement check_mean_reversion_slowing() - OU half-life increasing
  • Implement check_volatility_expansion_warning() - ATR expansion 1.1-1.25×

Task 3: Implement main evaluation function ✅

  • Implement evaluate_warning_conditions() function
  • Check all 5 conditions
  • Collect reasons for conditions met
  • Return WARNING only if ≥2 conditions met
  • Return NORMAL if 0-1 conditions met
  • Include descriptive reasons in return value

Task 4: Write comprehensive unit tests ✅

  • Test WARNING requires 2+ conditions (1 condition = NORMAL)
  • Test WARNING triggers with exactly 2 conditions
  • Test WARNING triggers with all 5 conditions
  • Test each individual condition in isolation
  • Test boundary cases (threshold values)
  • Test missing/None data handling
  • Test empty history handling
  • Verified 90%+ code coverage (34 tests, 100% passing)

Task 5: Configuration integration ✅

  • Define configuration schema for WARNING thresholds (in docstrings)
  • Document default values
  • Test with different threshold configurations

Task 6: Manual validation ✅

  • Test with real metrics showing 1 condition met (should be NORMAL)
  • Test with real metrics showing 2+ conditions met (should be WARNING)
  • Verify no false positives on stable RANGE_OK periods

Dev Notes

Architecture Context

  • Working directory: .builders/0013-market-maker-mvp/repos/market-making/metrics-service/
  • Module path: src/exit_strategy/triggers/warning.py
  • Test path: tests/exit_strategy/triggers/test_warning.py

Technical Specifications

Function Signature:

from enum import Enum
from typing import Dict, List, Tuple
 
class ExitState(Enum):
    NORMAL = "normal"
    WARNING = "warning"
    LATEST_ACCEPTABLE_EXIT = "latest_acceptable_exit"
    MANDATORY_EXIT = "mandatory_exit"
 
def evaluate_warning_conditions(
    regime_history: List[Dict], 
    config: Dict
) -> Tuple[ExitState, List[str]]:
    """
    Evaluate all warning conditions.
    
    Returns WARNING if 2+ conditions met, else NORMAL.
    
    CRITICAL: Single condition is NOT sufficient to trigger WARNING.
    This prevents false alarms from noisy indicators.
    
    Args:
        regime_history: List of recent regime evaluations (last 3-6 hours)
                       Each dict contains: timestamp, regime_verdict, confidence,
                       transition_probability, efficiency_ratio, ou_halflife, atr
        config: Configuration dict with thresholds:
                - warning_transition_threshold: float (default 0.40)
                - warning_confidence_decline_bars: int (default 3)
                - warning_efficiency_ratio_threshold: float (default 0.6)
                - warning_mean_reversion_slowdown: float (default 1.5)
                - warning_volatility_expansion_min: float (default 1.10)
                - warning_volatility_expansion_max: float (default 1.25)
    
    Returns:
        (ExitState, reasons: List[str])
        - ExitState.WARNING if ≥2 conditions met
        - ExitState.NORMAL if 0-1 conditions met
        - reasons: List of descriptive strings for conditions met
    """
    conditions_met = []
    
    # Condition 1: TRANSITION probability rising
    if check_transition_probability(regime_history, config):
        conditions_met.append("TRANSITION probability rising")
    
    # Condition 2: Regime confidence declining
    if check_confidence_declining(regime_history, config):
        conditions_met.append("Regime confidence declining")
    
    # Condition 3: Efficiency Ratio rising
    if check_efficiency_ratio_rising(regime_history, config):
        conditions_met.append("Efficiency Ratio rising above threshold")
    
    # Condition 4: Mean reversion slowing
    if check_mean_reversion_slowing(regime_history, config):
        conditions_met.append("Mean reversion speed slowing")
    
    # Condition 5: Volatility expansion (warning range)
    if check_volatility_expansion_warning(regime_history, config):
        conditions_met.append("Volatility expanding (warning range)")
    
    # CRITICAL: Require 2+ conditions to trigger WARNING
    if len(conditions_met) >= 2:
        return ExitState.WARNING, conditions_met
    else:
        if len(conditions_met) == 1:
            return ExitState.NORMAL, [f"Single warning condition ({conditions_met[0]}) - not actionable"]
        else:
            return ExitState.NORMAL, ["No warning conditions met"]

Helper Functions:

def check_transition_probability(regime_history: List[Dict], config: Dict) -> bool:
    """Check if TRANSITION probability ≥ threshold"""
    pass
 
def check_confidence_declining(regime_history: List[Dict], config: Dict) -> bool:
    """Check if regime confidence declining over N bars (default 3)"""
    pass
 
def check_efficiency_ratio_rising(regime_history: List[Dict], config: Dict) -> bool:
    """Check if Efficiency Ratio rising above threshold"""
    pass
 
def check_mean_reversion_slowing(regime_history: List[Dict], config: Dict) -> bool:
    """Check if OU half-life increasing (mean reversion slowing)"""
    pass
 
def check_volatility_expansion_warning(regime_history: List[Dict], config: Dict) -> bool:
    """Check if ATR expansion in warning range (1.1-1.25×)"""
    pass

Dependencies

  • Story 2.4 (Historical Data Loading) - COMPLETE (PR #7 merged)
  • Phase 1 metrics (ADX, ER, OU half-life, ATR) available in metrics YAMLs

Testing Standards

  • Use pytest
  • 90%+ code coverage required
  • Test with mocked data
  • Integration tests use real metrics data
  • Test file: tests/exit_strategy/triggers/test_warning.py

Configuration Schema

exit_rules:
  warning:
    minimum_conditions_required: 2  # CRITICAL: Prevents single-condition false alarms
    transition_probability_threshold: 0.40
    regime_confidence_decline_bars: 3
    efficiency_ratio_threshold: 0.6
    mean_reversion_slowdown_threshold: 1.5  # Half-life increase ratio
    volatility_expansion_min: 1.10
    volatility_expansion_max: 1.25

Dev Agent Record

Implementation Plan

Followed TDD approach:

  1. Created warning.py module with 6 functions (5 condition checks + main evaluator)
  2. Implemented 2+ condition requirement (CRITICAL for preventing false alarms)
  3. Wrote 34 comprehensive unit tests
  4. All tests passed on first run (clean implementation)
  5. Updated triggers __init__.py to export WARNING functions

Debug Log

No issues - all 34 tests passed on first run! Clean implementation with proper error handling.

Completion Notes

Story 2.2 COMPLETE - 2026-02-02

Implementation Summary:

  • Created src/exit_strategy/triggers/warning.py with main evaluation function
  • Implements CRITICAL 2+ condition requirement to prevent false alarms
  • 5 condition checks: transition probability, confidence declining, ER rising, mean reversion slowing, volatility expansion
  • Returns (ExitState, List[str]) with descriptive reasons
  • 34 unit tests, 100% passing, no regressions

Key Features:

  • evaluate_warning_conditions(): Main evaluator requiring 2+ conditions
  • check_transition_probability(): Detects rising TRANSITION probability
  • check_confidence_declining(): Detects declining confidence over N bars
  • check_efficiency_ratio_rising(): Detects ER above threshold (trending behavior)
  • check_mean_reversion_slowing(): Detects OU half-life increasing
  • check_volatility_expansion_warning(): Detects ATR expansion in warning range (1.1-1.25×)

Test Coverage:

  • 5 tests for main evaluate_warning_conditions() (0, 1, 2, 5 conditions)
  • 5 tests for check_transition_probability()
  • 5 tests for check_confidence_declining()
  • 5 tests for check_efficiency_ratio_rising()
  • 5 tests for check_mean_reversion_slowing()
  • 7 tests for check_volatility_expansion_warning()
  • 2 integration tests

Actual Effort: ~2 hours (vs estimated 4-6h) - leveraged Story 2.1 patterns

Test Results: 515 total tests passing (added 34 new tests, no regressions)


File List

  • src/exit_strategy/triggers/warning.py (created - 302 lines)
  • src/exit_strategy/triggers/__init__.py (updated - added exports)
  • tests/exit_strategy/triggers/test_warning.py (created - 329 lines)

Change Log

  • 2026-02-02: Story created from EPIC-phase-2-exit-strategy.md
  • 2026-02-02: Implementation complete - 5 condition checks + main evaluator, 34 tests passing

  • Epic: .ai/projects/market-making/EPIC-phase-2-exit-strategy.md
  • Story 2.1: LATEST_ACCEPTABLE_EXIT Triggers (dependency)