o
    í½¨j5C  ã                   @   sl  d Z ddlmZmZ ddlmZ ddlmZmZm	Z	m
Z
mZmZmZ dZdZdZdZd	Zd
ZdZdZdZeee	e
hZeehZddddddddddddddœdœ
ZddddddœZddd dd!œZdddd"œZdddddd#œZeG d$d%„ d%ƒƒZd&d'„ Zd(d)„ Z d*d+„ Z!d,d-„ Z"d.d/„ Z#d0d1„ Z$d2d3„ Z%d4d5„ Z&d6d7hZ'd8d9hZ(d:d;„ Z)	<	<dDd=e*d>e+d?e,d@e,dAef
dBdC„Z-d<S )Ea!  
fast_prediction_engine.py

[FEATURE 2026-09-11] Replacement prediction stage for the
"fast_prediction" decision engine (config.json's entry.decision_engine),
built after 2026-09-10's live session produced ZERO trades despite HGTY
reaching a 100%-confidence BREAKOUT_RETEST setup at least 8 separate
times across the day. Root cause traced to structure_engine.py's
structure_score needing ~20-40 minutes of real bars before a swing pivot
can even confirm (pivot_confirm_bars' honest causal lag), colliding with
entry_score.py's extension-cliff demanding a full 100 on it anyway.

Per explicit design decision reached over that review: don't recreate
the same trend_score/structure_score/entry_score/risk_score/
prediction_score sprawl. Instead classify a SMALL set of signals --
almost all of them already computed live by stream_features.py, just
not consumed this way yet -- into one descriptive prediction state:
Direction (UP/DOWN/NEUTRAL) + Confidence, plus named classifications
for logging/review (momentum, volume, vwap, ema9, resistance,
extension).

CONFIDENCE IS DESCRIPTIVE ONLY. Per explicit instruction: "Confidence
describes the prediction. It does not control the trade by itself."
This module never decides should_enter -- that is entirely
fast_entry_gate.py's job (hard/soft checklist + a short live-persistence
window). Nothing here is a floor gate.

WHY THIS NEEDED LESS NEW CODE THAN IT LOOKED LIKE: stream_features.py's
slope.slope_5m IS already "price slope over the last 5 one-minute bars"
(see stream_features.py's _horizon_slope_pct), and its
ema_slope_lookback_bars config already defaults to 5 -- the "5-bar
rolling window" this design calls for was already being computed on
every poll cycle, just not read this way. This module is a thin
classifier over already-live data, not a new math layer.
é    )Ú	dataclassÚfield)Ú
get_config)ÚVWAP_STRONG_SUPPORTÚVWAP_RECLAIMÚ	VWAP_HOLDÚVWAP_EXTENSIONÚVWAP_FAILUREÚVWAP_DECLININGÚ	VWAP_FLATÚUPÚDOWNÚNEUTRALÚSAFEÚCAUTIONÚ	TOO_CLOSEÚNORMALÚEXTENDEDÚSEVERELY_EXTENDEDé   ç      à?g      è?gš™™™™™Ù?é   g333333ó?g       @g      @g333333Ó?g      Ð?gš™™™™™É?)ÚmomentumÚvolumeÚvwapÚema)
Úmin_barsÚmin_relative_volumeÚresistance_safe_pctÚresistance_safe_atr_multÚresistance_caution_pctÚbreakout_override_lookback_barsÚbreakout_override_volume_accelÚextension_atr_extendedÚextension_atr_severeÚconfidence_weightsg      ð?gffffffæ?ç        )ÚStrongÚBuildingÚ	WeakeningÚDeterioratingÚNeutralg333333ã?gš™™™™™¹?)Ú	ExpandingÚSteadyÚ	Decliningr+   )ÚBullishr+   ÚBearish)r/   Ú
ReclaimingÚFadingr0   r+   c                   @   sÄ   e Zd ZU eed< eZeed< dZeed< dZ	eed< dZ
eed< dZeed< dZeed	< eZeed
< dZeed< dZeed< dZeed< dZeed< eZeed< eed�Zeed< dZeed< dS )ÚFastPredictionReadingÚsymbolÚ	directionr&   Ú
confidencer+   r   Úvolume_stateÚ
vwap_stateÚ
ema9_stateÚ
resistanceNÚresistance_levelÚresistance_sourceÚresistance_distance_pctFÚbreakout_in_progressÚ	extension)Údefault_factoryÚreasonsÚinsufficient_data)Ú__name__Ú
__module__Ú__qualname__ÚstrÚ__annotations__ÚDIRECTION_NEUTRALr5   r6   Úfloatr   r7   r8   r9   ÚRESISTANCE_SAFEr:   r;   r<   r=   r>   ÚboolÚEXTENSION_NORMALr?   r   ÚlistrA   rB   © rN   rN   ú5/var/www/screener/premarket/fast_prediction_engine.pyr3   ]   s    
 r3   c                 C   ó<   | du rdS |duo|dk}| dkr|rdS dS |rdS dS )a  slope_5m = price slope over the last 5 one-minute bars (already
    computed by stream_features.py -- see this module's docstring).
    momentum_acceleration = stream_features' cross-horizon waterfall
    (shorter horizons moving further/faster than the 5m one = building).Nr+   r   r'   r)   r(   r*   rN   )Úslope_5mÚmomentum_accelerationÚacceleratingrN   rN   rO   Ú_momentum_statep   s   rT   c                 C   s<   | d u s|d u r
dS ||d kr| dkrdS | dk rdS dS )Nr+   r   gÍÌÌÌÌÌð?r,   çffffffî?r.   r-   rN   )Úvolume_accelerationÚrelative_volumeÚcfgrN   rN   rO   Ú_volume_state}   s   rY   c                 C   s   | t v rdS | tv rdS dS )Nr/   r0   r+   )Ú_VWAP_BULLISHÚ_VWAP_BEARISH)ÚclassificationrN   rN   rO   Ú_vwap_state‡   s
   r]   c                 C   rP   )a1  Three-tier EMA relationship per explicit instruction: price-vs-EMA9
    is the immediate state, EMA9's own slope is the momentum tell (price
    can still read above a FLATTENING EMA9 -- that's "Fading", the exact
    "still green but momentum already deteriorating" case this whole
    review was about).Nr+   r   r/   r2   r1   r0   rN   )Úprice_vs_ema9_pctÚ
ema9_slopeÚrisingrN   rN   rO   Ú_ema9_state�   s   ra   c           	      C   s„   |rt dd„ |D ƒdd�nd}|pi  d¡}|pi  d¡}d|fd|fd	|ffD ]\}}|r?|| kr?||||  |  d
 f  S q(dS )aK  Nearest active resistance ABOVE current price, checked in the
    explicit priority order: session high -> premarket high ->
    previous-day high. previous-day high is optional/fault-tolerant --
    its absence just removes it from consideration, never blocks
    anything (see premarket_scanner.py's prev_day_high_out docstring).c                 s   ó   � | ]}|d  V  qdS ©ÚhNrN   ©Ú.0ÚbrN   rN   rO   Ú	<genexpr>£   ó   € z&_resistance_context.<locals>.<genexpr>N)ÚdefaultÚpm_highÚprev_day_highÚsession_highÚpremarket_highÚprevious_day_highç      Y@)NNN)ÚmaxÚget)	ÚpriceÚbarsÚpremarket_resultrX   rm   rk   rl   ÚnameÚlevelrN   rN   rO   Ú_resistance_context�   s   ÿ€rx   c                 C   sf   |d u s| sdS t |d t| ƒƒ}tdd„ | | d … D ƒƒ}||d k}|d uo.||d k}|o2|S )NFr!   c                 s   rb   rc   rN   re   rN   rN   rO   rh   ²   ri   z(_breakout_in_progress.<locals>.<genexpr>g+‡ÙÎ÷ï?r"   )ÚminÚlenrq   )rt   r;   rV   rX   ÚlookbackÚrecent_highÚtouched_or_throughrS   rN   rN   rO   Ú_breakout_in_progress®   s   r~   c                 C   sX   | d u rt S |r
t S |r|d | nd }| |d ks |r"| |kr"t S | |d kr*tS tS )Nr   r   r    )rJ   ÚRESISTANCE_CAUTIONÚRESISTANCE_TOO_CLOSE)Údistance_pctÚatr_pctr>   rX   Úatr_safe_pctrN   rN   rO   Ú_resistance_classification¸   s   r„   c                 C   sN   dd„ | |fD ƒ}|st S t|ƒ}||d kr|dv rtS ||d kr%tS t S )Nc                 S   s   g | ]
}|d urt |ƒ‘qS )N)Úabs)rf   ÚxrN   rN   rO   Ú
<listcomp>Æ   s    z-_extension_classification.<locals>.<listcomp>r$   )r)   r*   r#   )rL   rq   ÚEXTENSION_SEVEREÚEXTENSION_EXTENDED)Úprice_vs_ema9_atrÚvwap_distance_atrr   rX   ÚdistsÚmax_distrN   rN   rO   Ú_extension_classificationÅ   s   rŽ   r'   r)   r,   r-   c                 C   sâ   |d }t |  |d  t| |d   t| |d   t| |d   }|tkr-|d9 }n|tkr5|d9 }|tkr>|d9 }n|tkrF|d9 }t|d	 d
ƒ}	| t	v oX|t
v oX|tk}
|
rat}||	fS |dkrkt}||	fS t}||	fS )aJ  direction is now a strict unanimity gate over momentum, volume,
    and resistance only (see comment above -- vwap/ema9 intentionally
    excluded as of 2026-09-14). confidence stays a blended score over
    all four inputs, purely descriptive (shortlist ranking/logging
    only, see module docstring), unaffected by this change.r%   r   r   r   r   r   g333333ë?rU   rp   é   gffffffÖ?)Ú_MOMENTUM_SCOREÚ_VOLUME_SCOREÚ_VWAP_SCOREÚ
_EMA_SCORErˆ   r‰   r€   r   ÚroundÚ_POSITIVE_MOMENTUMÚ_POSITIVE_VOLUMErJ   ÚDIRECTION_UPÚDIRECTION_DOWNrH   )r   r7   r8   r9   r?   r:   rX   ÚwÚrawr6   Úall_positiver5   rN   rN   rO   Ú_direction_and_confidence  s8   ÿÿ

ÿýüÿrœ   Nr4   rt   ru   rX   Úreturnc                 C   s*  i t ¥|ptƒ  di ¡¥}t|ddƒrt| ddgd�S t|ƒ|d k r5t| dd|d › d	t|ƒ› �gd�S |j d
¡}|j d¡}t||ƒ}|j	 d¡}|j	 d¡}	t
||	|ƒ}
|j d¡}t|ƒ}|j d¡}|j d¡}t||ƒ}|j d¡}t||||ƒ\}}}t||||ƒ}|j d¡}t||||ƒ}|j d¡}|j d¡}t||||ƒ}t||
|||||ƒ\}}|du r¼dn|› d|d›d|d›d�|rÌdnd }d|› d|› d�d |
› d!|	› d"|› d�d#|› d|› d�d$|› d%|› d&|› d�d'|› d|› d�d(|› �g}t| ||||
|||||||||d)�S )*a  
    features: a stream_features.StreamFeatures reading, computed by the
        caller exactly as prediction_pipeline.py already does today --
        this module doesn't change how features are computed, only how
        they're read.
    bars: the same live 1-min bars features was computed from (needed
        here directly for session_high / breakout-in-progress).
    premarket_result: this symbol's premarket_scanner.py result dict
        (pm_high/pm_low/prev_day_high) -- optional, fail-soft throughout.
    Úfast_predictionrB   FTz*insufficient upstream stream_features data)r4   rB   rA   r   zwarm-up: need z one-minute bars, have rQ   rR   rV   rW   r\   r^   r_   Úlastr‚   rŠ   r‹   Nzclear overheadz $z.2fz (z% away)z2 [breakout in progress -- distance penalty waived]Ú z	momentum=z (slope_5m=ú)zvolume=z (rvol=z, accel=zvwap=zema9=z (price_vs_ema9=z%, ema9_slope=zresistance=z
extension=)r4   r5   r6   r   r7   r8   r9   r:   r;   r<   r=   r>   r?   rA   )ÚDEFAULT_CONFIGr   rr   Úgetattrr3   rz   ÚslopeÚaccelerationrT   r   rY   r   r]   r   ra   rs   rx   r~   Ú
volatilityr„   rŽ   rœ   )r4   Úfeaturesrt   ru   rX   rQ   rR   r   rV   rW   r7   Úvwap_classificationr8   r^   r_   r9   rs   Úres_nameÚ	res_levelÚres_distance_pctÚbreakoutr‚   Úresistance_staterŠ   r‹   Úextension_stater5   r6   Úresistance_descrA   rN   rN   rO   Úcompute_fast_prediction%  sf   ÿþ

ÿ
ÿÿú	ûr°   )NN).Ú__doc__Údataclassesr   r   Úconfig_loaderr   Ústream_featuresr   r   r   r   r	   r
   r   r—   r˜   rH   rJ   r   r€   rL   r‰   rˆ   rZ   r[   r¢   r�   r‘   r’   r“   r3   rT   rY   r]   ra   rx   r~   r„   rŽ   r•   r–   rœ   rF   rM   Údictr°   rN   rN   rN   rO   Ú<module>   sd    $$è

9#ÿÿÿ