o
     9£jÇC  ã                   @   s\  d Z ddlmZmZ ddlmZmZ ddlmZ dZdZ	dZ
i dd	“d
d“dd	“dd“dd	“dd“dd“dd“dd“dddddœ“dddddœ“d d“d!d"“d#d“d$d%“d&d'“d(d)“ZeG d*d+„ d+ƒƒZd,ed-efd.d/„Zd0ed-efd1d2„Zd0ed-efd3d4„Zd5ed0ed-efd6d7„Zd8ed9ed:ed-efd;d<„Z	=	=	=dCd>ed?ed8ed@ed0ed-efdAdB„Zd=S )Da�  
entry_score.py

[SIMULATION-ONLY 2026-09-05] Combines trend_engine/structure_engine/
prediction_engine/setup_engine/regime_engine into FIVE SEPARATE scores
(trend, structure, prediction, entry, risk) and one action decision
(WAIT / CONFIRMING / READY) -- deliberately NOT one blended "health"
number, per the project's original design brief's explicit section 24
instruction and its own worked example: a symbol can be Trend=94,
Prediction=88, Entry=51, Risk=76 and still resolve to WAIT, because
Entry (setup/trigger readiness) is what's actually low, and no amount
of trend or prediction strength should paper over that.

CONFIRMATION WINDOW (section 25): "never enter from one tick." Needs a
caller-held confirmation_state dict, same pattern as every other
caller-held-history piece built this session (stream_features.py's
`prior`, prediction_engine.py's `prior`) -- this module stays stateless,
the orchestrator holds {symbol: confirmation_state} across calls. Any
single reading that fails the thresholds immediately resets the window
(an ABORT, per the design brief's own worked example of a declining
reading sequence) rather than averaging through it.

Not consumed anywhere in the live bot yet -- see config.json's
entry_score._note. opportunity_score is deliberately NOT computed here
-- section 26 makes that explicitly opportunity_ranker.py's job (it
ranks ACROSS symbols; this module only ever looks at one).
é    )Ú	dataclassÚfield)ÚdatetimeÚtimezone)Ú
get_configÚWAITÚ
CONFIRMINGÚREADYÚmin_trend_scoreé<   Úmin_structure_scoreé2   Úmin_prediction_scoreÚmin_entry_scoreéA   Úmin_risk_scoreÚmax_spread_pctç      ð?Úconfirmation_secondsé   Úconfirmation_min_readingsé   Úmin_reward_risk_ratiog      ø?Úentry_weightsç      à?g333333Ó?gš™™™™™É?)Úsetup_confidenceÚregime_allowsÚprediction_momentumÚrisk_weightsgš™™™™™Ù?)Ústop_qualityÚ	liquidityÚspreadÚmin_relative_volumeÚ1require_full_confirmation_when_extended_from_openTÚmax_extension_from_open_pctÚfull_confirmation_min_scoreéd   Úfull_confirmation_relax_at_pctg      @Úextended_min_scoreéR   c                   @   s´   e Zd ZU eed< 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	< eed
�Zeed< eed
�Zeed< dZeed< dZeed< eed
�Zeed< dS )ÚEntryScoreReadingÚsymbolÚcomputed_atç        Útrend_scoreÚstructure_scoreÚprediction_scoreÚentry_scoreÚ
risk_scoreÚaction)Údefault_factoryÚreasonsÚconfirmationFÚthresholds_clearedÚinsufficient_dataÚmissingN)Ú__name__Ú
__module__Ú__qualname__ÚstrÚ__annotations__r   r.   Úfloatr/   r0   r1   r2   ÚACTION_WAITr3   r   Úlistr5   Údictr6   r7   Úboolr8   r9   © rD   rD   ú*/var/www/screener/premarket/entry_score.pyr*   ;   s   
 r*   ÚxÚreturnc                 C   s   t dtd| ƒƒS )Nr-   r   )ÚmaxÚmin)rF   rD   rD   rE   Ú_clamp01L   s   rJ   Úcfgc              	   C   sº  g }d }|rdd„ |  ¡ D ƒ}t|dd„ d d�}|d ur!|jd nd}|d u r,| d¡ |d ur6t|d	d ƒnd }	|d urb|	d urb|j|	v rGd
nd}
|
dkra| dt|ddƒ› d|j› d|	› d�¡ n|	d urx|	sxd}
| dt|ddƒ› d�¡ nd}
|d ur„t|ddƒnd}|dkr�d
}n|dk r�d}| d|› d�¡ nd}|d }||d  |
|d   ||d   d }|d urÕ|jrÕ|	d u sÇ|j|	v rÕ| d|j› d|j› d�¡ t|dƒ||fS )Nc                 S   s   g | ]}|j r|‘qS rD   )Úvalid)Ú.0ÚrrD   rD   rE   Ú
<listcomp>T   s    z,_compute_entry_component.<locals>.<listcomp>c                 S   s   | j S )N)Ú
confidence)rN   rD   rD   rE   Ú<lambda>U   s    z*_compute_entry_component.<locals>.<lambda>)ÚkeyÚdefaultç      Y@r-   zno setup currently validatesÚallowed_setupsr   zregime=ÚregimeÚ?z does not permit z (allowed: ú)z permits no setups right nowr   Úsloper   z&prediction score deteriorating (slope=r   r   r   r   zsetup z
 valid at z% confidencer   )ÚvaluesrH   rP   ÚappendÚgetattrÚsetuprL   Úround)Ú	structurerV   ÚsetupsÚ
predictionrK   r5   ÚbestrL   r   Úallowedr   Ú
pred_sloper   Úwr1   rD   rD   rE   Ú_compute_entry_componentP   sP   
ÿ
ÿ€

ÿ
þþ rf   c                 C   sÂ  g }| j  d¡}| j d¡}|j}|dur&|dur&||kr&|| | d }n|r6|r6|| d }| d¡ nd}d}	|durJ|jrJt|j dd¡ƒ}	|rv|dkrv|	durv|	| }
t|
|d	  ƒ}|
|d	 k ru| d
|
d›d|d	 › d�¡ nd}| d¡ | j d¡}|dur�t||d  ƒnd}| j	 d¡}|dur»td||d   ƒ}||d krº| d|d›d|d › d�¡ nd}| d¡ |d }||d  ||d   ||d   d }t
|dƒ|fS )a£  
    [FEATURE 2026-09-05] Structure-aware risk quality -- per section 27's
    explicit "risk_per_share = entry_price - structure_stop" concept.
    Uses structure.last_swing_low as the natural stop reference (the
    same level structure_engine already identifies as the last
    confirmed support) rather than an arbitrary distance. This module
    only SCORES that quality -- it does not compute actual position size
    or replace risk_manager.py, per the project's explicit "do not
    replace the existing risk engine" instruction; risk_manager.py's
    real compute_position_size()/compute_initial_stop() remain the only
    functions that ever size a real trade.
    ÚlastÚatrNrT   zJno structural swing-low stop available -- using ATR-based estimate insteadÚ5mr-   r   r   zreward/risk ratio ú.2fú below minimum z/ (stop would be too wide for the expected move)r   zOcould not compute a reward/risk ratio (missing stop reference or expected move)Úrelative_volumer"   Ú
spread_pcté   r   úspread ú% exceeds max ú%z,no quote available -- spread quality unknownr   r   r    r!   r   )ÚpriceÚgetÚ
volatilityÚlast_swing_lowr[   Úexpected_move_pctÚabsrJ   ÚvolumeÚquoter^   )Úfeaturesr_   ra   rK   r5   rr   rh   Ústructural_stopÚrisk_per_share_pctÚexpected_reward_pctÚreward_risk_ratior   Úrvolr    rm   Úspread_qualityre   r2   rD   rD   rE   Ú_compute_risk_component{   sP   
ÿ€
€

ÿÿr�   Úextension_from_openc                 C   sL   |d }|d }|d }|d }| |kr|S | | ||  }||||   S )a®  
    [UPDATED 2026-09-10] Graduated decay, replacing the original hard
    cliff (see compute_entry_score's extended-from-open comment for why
    that cliff was added -- closing the BGC gap). Confirmed on
    2026-09-10's real session: HGTY hit BREAKOUT_RETEST at 100%
    confidence at least 8 separate times across the day (all-day climb,
    +10% off open by afternoon, no sharp pullback) and was rejected
    every time by the old all-or-nothing cliff, because structure_score
    (structure_engine.py) cannot read 100 until enough bars exist for a
    swing pivot to actually confirm -- an honest ~20-40 minute lag, not
    a bug (see structure_engine.py's pivot_confirm_bars). The cliff
    demanded 100% the instant a symbol crossed max_extension_from_open_pct,
    which for a gapper happens within the first minute or two -- exactly
    when structure_score is structurally incapable of being anything but
    low. This keeps the cliff's own protection fully intact AT the
    boundary (still 100% right at max_extension_from_open_pct, so the
    BGC case -- caught within its first ~2.7% of extension -- is still
    caught) and only relaxes as a symbol proves itself further extended
    without the setup falling apart, down to extended_min_score by
    full_confirmation_relax_at_pct, never lower (a floor, not a ramp to
    zero -- a badly extended runner still needs a near-flawless setup,
    just not a mathematically perfect one).
    r$   r'   r%   r(   rD   )r‚   rK   ÚnearÚfarÚfullÚrelaxedÚtrD   rD   rE   Ú_extended_confirmation_floorµ   s   rˆ   Úconfirmation_stater7   Únowc                 C   s:   |sdddœS |   d¡}|du r|}||   dd¡d dœS )aŽ  
    Caller-held confirmation tracking. Pass the PREVIOUS call's returned
    dict back in as `confirmation_state` (an empty dict on the first
    call for a symbol). ANY reading with thresholds_cleared=False resets
    the window immediately -- no partial credit, matching the design
    brief's own "81, 78, 69, 57 -> ABORT" example (a declining sequence
    aborts, it doesn't average out).
    Nr   )ÚsinceÚreadingsr‹   rŒ   rn   )rs   )r‰   r7   rŠ   r‹   rD   rD   rE   Úupdate_confirmation_state×   s   	

r�   Nr+   r`   Úas_ofc
           %      C   sÂ  i t ¥|	ptƒ  di ¡¥}	|pi }|pt tj¡}
g }d|fd|fd|fd|ffD ]\}}t|ddƒr8| |¡ q)|rOt	|d|
ƒ}t
| |
d||d	|› �gd
�S |j}|j}|j}t|||||	ƒ\}}}t||||	ƒ\}}t|ƒt|ƒ }|j d¡}|du pƒ||	d k}|j d¡}|	d o—|duo—||	d k}|rìtt||	ƒdƒ}t||	d ƒ}t||	d ƒ}t||	d ƒ}t||	d ƒ} t||	d ƒ}!| d|d›d|	d › d|› d|	d › d|	d › d|	d › d |	d! › d"�¡ n|	d }|	d }|	d }|	d } |	d }!||k�o||k�o||k�o|| k�o||!k�o|}"|"�s||k �r.| d#|› d$|› �¡ ||k �r>| d%|› d$|› �¡ ||k �rN| d&|› d$|› �¡ || k �r^| d'|› d$| › �¡ ||!k �rn| d(|› d$|!› �¡ |�s| d)|› d*|	d › d+�¡ t	||"|
ƒ}|"�s‹t}#nG|d, �r˜|
|d,   ¡ nd-}$|$|	d. k�r¼|d/ |	d0 k�r¼t}#| d1|$d2›d3|d/ › d4�¡ nt}#| d5|$d2›d6|	d. › d7|d/ › d8�¡ t
| |
||||||#|||"d9�S ):ap  
    as_of: [FEATURE 2026-09-05] the moment this reading is "as of" --
    drives the confirmation window's elapsed-time tracking. Defaults to
    real wall-clock time (correct for live use). A REPLAY/BACKTEST
    caller MUST pass the bar's own timestamp here -- otherwise the
    confirmation window would measure the REAL time the backtest script
    happens to take to execute, not simulated market time, making
    CONFIRMING -> READY transitions meaningless (either impossible if
    the script runs faster than confirmation_seconds, or requiring an
    actual multi-second sleep per symbol per bar if it doesn't).
    r1   rz   Útrendr_   ra   r8   FTzinsufficient upstream data: )r+   r,   r8   r9   r6   r5   rm   Nr   Úextension_from_open_pctr#   r$   rn   r
   r   r   r   r   zprice rj   z% above open (max z%) -- requires z:% confirmation on every score before entry (decaying from r%   z% at z% extension to r(   z% by r'   z%)ztrend_score rk   zstructure_score zprediction_score zentry_score zrisk_score ro   rp   rq   r‹   r-   r   rŒ   r   zall thresholds held for z.1fz	s across z readings -- confirmedz thresholds cleared, confirming (zs / zs, z reading(s)))r+   r,   r.   r/   r0   r1   r2   r3   r5   r6   r7   )ÚDEFAULT_CONFIGr   rs   r   rŠ   r   Úutcr\   r[   r�   r*   Úscorerf   r�   rA   ry   rr   r^   rˆ   rH   r@   Útotal_secondsÚACTION_READYÚACTION_CONFIRMING)%r+   rz   r�   r_   ra   rV   r`   r‰   rŽ   rK   rŠ   r9   ÚnameÚobjÚnew_confr.   r/   r0   r1   Úentry_reasonsÚbest_setup_resultr2   Úrisk_reasonsr5   rm   Ú	spread_okr‚   Úextended_from_openÚfloorÚ	min_trendÚmin_structureÚmin_predictionÚ	min_entryÚmin_riskr7   r3   ÚelapsedrD   rD   rE   Úcompute_entry_scoreè   sÂ   ÿ
€
þ

ÿÿ
ýÿþþýýÿ
ÿþýüú	




  
ÿýr¦   )NNNNN)Ú__doc__Údataclassesr   r   r   r   Úconfig_loaderr   r@   r–   r•   r‘   r*   r?   rJ   rB   Útuplerf   r�   rˆ   rC   r�   r=   r¦   rD   rD   rD   rE   Ú<module>   s~    ÿþýüûúùø	÷
öõôóòñðï+:"ýÿþþýý