institutional-trader/backend/python_service/services/flow_decay_validator.py

119 lines
4.4 KiB
Python

"""
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