""" trend_detector.py — Trend strength and maturity helpers. Utility functions for computing trend alignment, acceleration, persistence. Used by regime_detector and price_structure scorer. """ import numpy as np import pandas as pd def ema_alignment_score(close: float, ema20: float, ema60: float, ema120: float) -> float: """Score EMA alignment: 0=bearish, 50=neutral, 100=bullish.""" if any(pd.isna(x) for x in [ema20, ema60, ema120]): return 50.0 alignments = 0 if ema20 > ema60: alignments += 1 if ema60 > ema120: alignments += 1 if ema20 > ema120: alignments += 1 if alignments == 3: return 85.0 elif alignments == 2: return 65.0 elif alignments == 1: return 35.0 else: return 15.0 def adx_trend_score(adx: float) -> float: """Convert ADX value to trend score: 0-100.""" if pd.isna(adx): return 50.0 if adx > 40: return 90.0 elif adx > 25: return 60.0 + (adx - 25) / 15 * 30 elif adx > 15: return 40.0 + (adx - 15) / 10 * 20 else: return max(10.0, adx / 15 * 40) def breadth_persistence(breadth_scores: list[float], window: int = 5) -> float: """How consistently has breadth stayed at its current level? 0-100.""" if len(breadth_scores) < window: return 50.0 recent = breadth_scores[-window:] mean_val = np.mean(recent) std_val = np.std(recent) if len(recent) > 1 else 0 # Low std = high persistence persistence = 100 - min(std_val * 5, 100) # Bias: higher breadth = higher persistence score return persistence * 0.5 + mean_val * 0.5 def trend_strength_composite(ema_score: float, adx_score: float, breadth_score: float) -> float: """Composite trend strength 0-100.""" return ema_score * 0.25 + adx_score * 0.25 + breadth_score * 0.50 def compute_maturity(trend_strength: float, breadth_persistence: float, vol_expansion: float) -> float: """ Compute regime maturity score 0-100. EMERGING (0-30): trend accelerating, breadth expanding CONFIRMED (30-70): trend stable, breadth stable EXHAUSTING (70-100): trend decelerating, breadth contracting, vol abnormal """ return ( trend_strength * 0.50 + breadth_persistence * 0.30 + (100 - vol_expansion) * 0.20 # inverted: low vol = early stage )