""" Flow Decay & Reversal Validation Service Validates flow decay/reversal signals with anchors (premium, dealer pressure, price levels) """ import pandas as pd import numpy as np from typing import Optional from enum import Enum from utils.logger import logger class FlowState(Enum): """Flow state classification""" ACTIONABLE = "ACTIONABLE" # Flow decay/reversal is actionable (trade signal) INFORMATIONAL = "INFORMATIONAL" # Flow decay/reversal is informational only (no trade signal) class FlowDecayValidator: """ Validates flow decay and reversal signals. Flow decay/reversal is actionable ONLY IF: - Premium contracts (high relative premium) - Dealer hedge pressure decreases - Price fails near VWAP / opening range / key level Otherwise mark as INFORMATIONAL. """ def __init__(self): # Configuration self.min_relative_premium_for_actionable = 60.0 # Minimum relative premium score self.vwap_failure_threshold_pct = 0.5 # Price within 0.5% of VWAP = failure self.dealer_pressure_decrease_threshold = 20.0 # Dealer pressure decrease threshold def validate_flow_decay_reversal( self, df: pd.DataFrame, row_idx: int ) -> str: """ Validate if flow decay/reversal is actionable. Returns FlowState enum value as string. """ if row_idx >= len(df): return FlowState.INFORMATIONAL.value current_row = df.iloc[row_idx] # Check 1: Premium contracts (relative premium score) relative_premium_score = current_row.get('relative_premium_score', 0) or 0 if relative_premium_score < self.min_relative_premium_for_actionable: return FlowState.INFORMATIONAL.value # Check 2: Dealer hedge pressure decreases # Look at recent dealer pressure trend symbol = current_row.get('symbol_norm') flow_ts_utc = current_row.get('flow_ts_utc') if pd.notna(symbol) and pd.notna(flow_ts_utc): from datetime import timedelta window_start = flow_ts_utc - timedelta(minutes=30) mask = ( (df['symbol_norm'] == symbol.upper()) & (df['flow_ts_utc'] >= window_start) & (df['flow_ts_utc'] <= flow_ts_utc) & (df['dealer_hedge_pressure_score'].notna()) ) recent_pressure = df[mask]['dealer_hedge_pressure_score'] if len(recent_pressure) >= 2: current_pressure = current_row.get('dealer_hedge_pressure_score', 0) or 0 recent_avg = recent_pressure.iloc[:-1].mean() pressure_decrease = recent_avg - current_pressure if pressure_decrease < self.dealer_pressure_decrease_threshold: return FlowState.INFORMATIONAL.value # Check 3: Price fails near VWAP / opening range / key level price_vs_vwap_pct = current_row.get('price_vs_vwap_pct') pct_vs_rth_open = current_row.get('pct_vs_rth_open') price_failure = False # VWAP failure if price_vs_vwap_pct is not None and not pd.isna(price_vs_vwap_pct): if abs(price_vs_vwap_pct) <= self.vwap_failure_threshold_pct: price_failure = True # Opening range failure if pct_vs_rth_open is not None and not pd.isna(pct_vs_rth_open): if abs(pct_vs_rth_open) <= 0.3: # Within 0.3% of open price_failure = True if not price_failure: return FlowState.INFORMATIONAL.value # All checks passed: actionable return FlowState.ACTIONABLE.value def enrich_with_flow_state(self, df: pd.DataFrame) -> pd.DataFrame: """ Add flow state validation to DataFrame. Adds: flow_state """ if df.empty: return df df = df.copy() df['flow_state'] = FlowState.ACTIONABLE.value # Default to actionable # Only validate flow decay/reversal cases # For now, mark all as actionable (can be enhanced based on flow_acceleration, follow_on_ratio) # This is a placeholder - in production, you'd identify decay/reversal patterns first logger.info(f"Flow state validation complete. Actionable: {(df['flow_state'] == FlowState.ACTIONABLE.value).sum()}") return df