o
    z³¨j¦,  ã                   @   s¨   d Z ddlmZmZ ddlmZmZ ddlZddlmZ ddl	m
Z
mZmZ dZdZd	Zd
dddddœZeG dd„ dƒƒZdd„ Z	ddededededef
dd„ZdS )añ  
fast_entry_gate.py

[FEATURE 2026-09-11] Entry validation + short live-confirmation layer for
the "fast_prediction" decision engine -- see fast_prediction_engine.py's
module docstring for the full design history this replaces (structure_
engine.py/prediction_engine.py/entry_score.py's floor-gated architecture).

fast_prediction_engine.py's Direction/Confidence is descriptive only and
never gates entry by itself, per explicit instruction ("Confidence
describes the prediction. It does not control the trade by itself.").
This module is the ONLY thing that decides BUY / WAIT / REJECT.

STATE MACHINE (caller-held persistence_state dict per symbol, same
contract as entry_score.py's confirmation_state -- monitor.py owns one
per symbol via Monitor._fast_engine_state):

  - A HARD disqualifier (price loses VWAP, VWAP slope turns negative,
    price acceleration turns sharply negative, resistance is TOO_CLOSE
    with no breakout in progress, spread unacceptable, or severely
    extended while momentum deteriorates) -> REJECT, persistence_state
    fully cleared. Per explicit instruction this does NOT ban the
    symbol -- the very next poll evaluates it completely fresh ("reset
    and rewatch in place"). No separate cooldown/benching invented here.

  - No hard disqualifier AND direction == UP -> the setup is "alive":
    the persistence timer starts (or keeps running) and only flips to
    BUY once it has held continuously for persistence_seconds (15-20s
    range, per explicit instruction -- NOT the 60-90s originally
    floated, and NOT the old architecture's multi-minute structure-score
    ramp-up). A SOFT condition being momentarily off (e.g. volume_
    acceleration cooling, EMA9/EMA20 context misaligned) does not reset
    the timer -- only a HARD disqualifier does, matching the explicit
    "one minor condition disappears -> WAIT, don't kill the whole
    attempt" instruction.

  - direction != UP with no hard disqualifier -> WAIT, timer cleared
    (nothing bullish is actually being timed yet).
é    )Ú	dataclassÚfield)ÚdatetimeÚtimezoneN)Ú
get_config)ÚDIRECTION_UPÚRESISTANCE_TOO_CLOSEÚEXTENSION_SEVEREÚBUYÚWAITÚREJECTé   g      ð?g      à¿gš™™™™™Ù?é   )Úpersistence_secondsÚmax_spread_pctÚ hard_price_acceleration_negativeÚmin_volume_pace_ratioÚmax_evaluation_gap_secondsc                   @   sp   e Zd ZU 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< eed�Zeed	< eed�Zeed
< dS )ÚFastGateDecisionÚsymbolÚstateFÚshould_enterg        Úpersistence_seconds_elapsed)Údefault_factoryÚreasons_forÚreasons_againstÚpersistenceN)Ú__name__Ú
__module__Ú__qualname__ÚstrÚ__annotations__Ú
STATE_WAITr   r   Úboolr   Úfloatr   Úlistr   r   Údictr   © r'   r'   ú./var/www/screener/premarket/fast_entry_gate.pyr   c   s   
 r   c                 C   sœ  g }|j  d¡}|j d¡}|dur'|dur'||k r'| d|d›d|d›�¡ |j d¡}|dur:|dk r:| d	¡ |j d
¡}|durS||d k rS| d|› d�¡ |j d¡}|durr||d krr| d|d›d|d › d�¡ | jtkrŠ| j}	| |	dur‡d|	d›d�nd¡ | j	t
kr”| d¡ |j d¡}
|
durÌt ¡ t ¡  }|dkrÌ|
| }||d k rÌ| d|d›d|
› d|d d›d|d › d�	¡ |S ) aÜ  Every item here is one of the explicit REJECT examples: 'price
    falls below VWAP', 'price acceleration turns strongly negative',
    'VWAP slope turns negative', 'resistance rejects price with heavy
    selling' (modeled here as TOO_CLOSE-with-no-breakout-in-progress --
    the prediction engine's own resistance classification already
    accounts for the breakout exception), 'spread becomes unacceptable',
    'extreme overextension occurs while momentum deteriorates'.ÚlastÚvalueNzprice $z.2fz fell below VWAP $Úsloper   zVWAP slope turned negativeÚmomentum_accelerationr   z%price acceleration sharply negative (ú)Ú
spread_pctr   zspread z% exceeds max ú%zresistance too close (z % away, no breakout in progress)zresistance too closezAseverely extended while momentum deteriorates -- chase protectionÚrelative_volumer   zvolume pace zx normal (rvol=z, éd   ú.1fz"% of session elapsed) below floor z x -- too illiquid to safely exit)ÚpriceÚgetÚvwapÚappendÚaccelerationÚquoteÚ
resistancer   Úresistance_distance_pctÚ	extensionr	   ÚvolumeÚmarket_timeÚminutes_since_openÚsession_length_minutes)Ú
predictionÚfeaturesÚcfgÚreasonsr3   Ú
vwap_valueÚ
vwap_slopeÚaccelr.   Údistr0   Úelapsed_fractionÚ
pace_ratior'   r'   r(   Ú_hard_disqualifiersn   sJ   

ÿÿ

ÿþÿrJ   r   Úpersistence_stateÚas_ofrB   Úreturnc              
   C   sx  i t ¥|ptƒ  di ¡¥}|pt tj¡}|pi }t|ddƒr*t| t	|j
ddid�S t|||ƒ}|r?t| t||j
 ddid�S |jtkrWt| t	d|j› d�g|j
 ddid�S | d¡}| d	¡}	|durw|	durw||	  ¡ }
|
|d
 krwd}|pz|}||  ¡ }||d kr¢t| td|d|d›d|d › d�g|j
 ||dœd�S t| t	|d|d›d|d › d�g|j
 ||dœd�S )a¢  
    persistence_state: caller-held {"since": datetime_or_None,
        "last_seen": datetime_or_None}, fed back in on every call for the
        same symbol (fresh {} = cold start). "last_seen" is the as_of time
        of the last call that reached the UP-tracking branch below --
        used only to detect a stale "since" (see max_evaluation_gap_seconds
        above), not exposed outside this module.
    as_of: real wall-clock time by default; a replay/backtest caller must
        pass the bar's own timestamp, same contract as every other
        as_of-taking engine in this project (entry_score.compute_entry_score,
        stream_features.compute_features).
    Úfast_prediction_gateÚinsufficient_dataFÚsinceN)r   r   r   r   z
direction=z, not UPÚ	last_seenr   r   Tzheld r2   zs (>= zs))rP   rQ   )r   r   r   r   r   r   zholding zs / Ús)r   r   r   r   r   )ÚDEFAULT_CONFIGr   r4   r   Únowr   ÚutcÚgetattrr   r"   rC   rJ   ÚSTATE_REJECTÚ	directionr   Útotal_secondsÚ	STATE_BUY)r   r@   rA   rK   rL   rB   rT   Úhard_reasonsrP   rQ   ÚgapÚelapsedr'   r'   r(   Úevaluate_fast_entryž   sL   
ÿþ
ý

ýýr^   )NN)Ú__doc__Údataclassesr   r   r   r   r=   Úconfig_loaderr   Úfast_prediction_enginer   r   r	   rZ   r"   rW   rS   r   rJ   r    r&   r^   r'   r'   r'   r(   Ú<module>   s4    (Õ/
1ÿÿÿÿ