o
    é§›j  ã                	   @   sš   d Z ddlmZmZ ddlmZ ddlmZ dddddddd	d
œdœZdZ	dZ
dZdZdZeG dd„ dƒƒZdededefdd„Zddedefdd„ZdS )a¢  
trend_engine.py

[SIMULATION-ONLY 2026-09-05] Combines stream_features.py's multi-horizon
slope readings into a single trend judgment: which of five states is
this symbol in, how strong is that trend (0-100, direction-agnostic),
and is it strengthening or weakening right now. Not consumed anywhere in
the live bot yet -- see config.json's trend_engine._note.

Deliberately reuses intraday_health.py's own scoring PATTERN (a
transparent weighted breakdown of per-signal credit, not a new scheme)
rather than inventing a different style of score for this module --
same "explainable, not a black box" requirement, applied consistently.

DIRECTION vs STATE vs SCORE vs TREND_SLOPE -- four different questions,
kept separate per the project's original design brief's own example
("Trend Score = 87, Trend Direction = UP, Trend Momentum = +12" are
three independent numbers, not one signed scale):

    direction   "UP" / "DOWN" / "NEUTRAL" -- which way, from the
                LONGEST available horizon (slope_5m, falling back to
                slope_3m, then vwap_slope) -- the underlying trend, not
                whatever the noisiest short horizon says.
    state       STRONG_UPTREND / WEAK_UPTREND / NEUTRAL / WEAK_DOWNTREND
                / STRONG_DOWNTREND -- direction + score bucketed into
                the five states.
    score       0-100, how strongly ALL SIX horizons (slope_sub,
                slope_1m, slope_3m, slope_5m, vwap_slope, ema9_slope)
                agree with `direction` -- direction-agnostic magnitude,
                not signed.
    trend_slope  positive = strengthening, negative = weakening.
                Reuses stream_features.py's acceleration.
                momentum_acceleration directly (the cross-horizon
                waterfall comparison) rather than recomputing the same
                thing under a different name -- see that module's
                docstring for what it actually measures.
é    )Ú	dataclassÚfield)Ú
get_config)Úclassify_slopegš™™™™™©?éF   é   é   é
   ©Ú	slope_subÚslope_1mÚslope_3mÚslope_5mÚ
vwap_slopeÚ
ema9_slope)Úflat_slope_threshold_pctÚstrong_trend_score_thresholdÚweightsÚSTRONG_UPTRENDÚWEAK_UPTRENDÚNEUTRALÚWEAK_DOWNTRENDÚSTRONG_DOWNTRENDc                   @   sv   e Zd ZU eed< dZeed< eZeed< dZe	ed< dZ
e	ed< eed�Zeed	< d
Zeed< eed�Zeed< dS )ÚTrendReadingÚsymbolr   Ú	directionÚstateç        ÚscoreÚtrend_slope)Údefault_factoryÚ	breakdownFÚinsufficient_dataÚmissingN)Ú__name__Ú
__module__Ú__qualname__ÚstrÚ__annotations__r   ÚSTATE_NEUTRALr   r   Úfloatr   r   Údictr!   r"   ÚboolÚlistr#   © r.   r.   ú+/var/www/screener/premarket/trend_engine.pyr   @   s   
 r   ÚslopesÚflat_thresholdÚreturnc                 C   s>   dD ]}|   |¡}|durt||ƒ}ddddœ|   S qdS )aH  The underlying trend direction, from the longest available
    horizon first -- see module docstring. Falls through to shorter
    horizons only when longer ones are genuinely missing (not merely
    flat -- a flat slope_5m legitimately means NEUTRAL, that's not a
    reason to fall back to a shorter, noisier horizon instead).)r   r   r   r   r   r   NÚUPÚDOWNr   )ÚpositiveÚnegativeÚflat)Úgetr   )r0   r1   ÚkeyÚvalÚclsr.   r.   r/   Ú_primary_directionL   s   

þr<   NÚcfgc              
      s  i t ¥|ptƒ  di ¡¥}i t d ¥| di ¡¥‰ |d }t| ddƒr,t| jddgd�S | j d	¡| j d
¡| j d¡| j d¡| j d¡| j d¡dœ}dd„ | 	¡ D ƒ}dd„ | 	¡ D ƒ}|sit| jd|d�S t
||ƒ}i }| 	¡ D ]B\}}	t|	|ƒ}
|dkr�|
dkr‡dn|
dkr�dnd}n|dkr£|
dkršdn|
dkr dnd}n|
dkr©dnd}tˆ | | dƒ||< qtt‡ fdd„|D ƒƒ}|rÑtt| ¡ ƒ| d dƒnd}|d  }|dkrä||krátnt}n|dkrñ||krîtnt}nt}| j d!d¡pûd}t| j|||t|d"ƒ|d|d#�S )$z‰
    features: a stream_features.StreamFeatures reading (or any object
    exposing the same .slope / .vwap / .ema dict attributes).
    Útrend_enginer   r   r"   FTz:trend -- upstream stream_features reading was insufficient)r   r"   r#   r   r   r   r   Úsloper   r
   c                 S   s   i | ]\}}|d ur||“qS ©Nr.   ©Ú.0ÚkÚvr.   r.   r/   Ú
<dictcomp>o   s    z"classify_trend.<locals>.<dictcomp>c                 S   s   g | ]
\}}|d u r|‘qS r@   r.   rA   r.   r.   r/   Ú
<listcomp>p   s    z"classify_trend.<locals>.<listcomp>r3   r5   g      ð?r7   g      à?r   r4   r6   é   c                 3   s   � | ]}ˆ | V  qd S r@   r.   )rB   rC   ©r   r.   r/   Ú	<genexpr>‚   s   € z!classify_trend.<locals>.<genexpr>g      Y@r   Úmomentum_accelerationé   )r   r   r   r   r   r!   r"   r#   )ÚDEFAULT_CONFIGr   r8   Úgetattrr   r   r?   ÚvwapÚemaÚitemsr<   r   ÚroundÚsumÚvaluesÚSTATE_STRONG_UPÚSTATE_WEAK_UPÚSTATE_STRONG_DOWNÚSTATE_WEAK_DOWNr)   Úacceleration)Úfeaturesr=   r1   r0   Ú	availabler#   r   r!   Únamer:   r;   ÚcreditÚweight_totalr   Ústrong_thresholdr   r   r.   rH   r/   Úclassify_trendZ   sT   ÿ





ú

"
ýr_   r@   )Ú__doc__Údataclassesr   r   Úconfig_loaderr   Ú
indicatorsr   rL   rT   rU   r)   rW   rV   r   r+   r*   r'   r<   r_   r.   r.   r.   r/   Ú<module>   s.    &úý