82 lines
2.4 KiB
Python
82 lines
2.4 KiB
Python
"""
|
|
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
|
|
)
|