o
    8œ©j
;  ã                   @   sÂ   d Z ddlmZ ddlmZmZmZmZmZm	Z	m
Z
mZmZmZmZmZmZmZ ddlmZmZmZmZmZmZmZmZmZmZ ddd„Z				dd
ede de!de!de!de!de!de!de"fdd„Z#d	S )u  
scorer.py

Transparent scoring engine. Every score comes with a `breakdown` dict
showing exactly how many points each factor contributed, so a stock's
rank is always explainable â€” never a black box, per project requirement.

Entry point:
    score_premarket_candidate(symbol, bars, pm_high, pm_low, avg_vol_baseline)
        -> used by premarket_scanner.py for the single full-universe scan
           at schedule.premarket_scan_time (see monitor.py's module
           docstring -- the 09:00-09:25 development-monitoring window and
           09:25 final-scoring review that used to call this repeatedly
           per symbol were eliminated 2026-09-01)

All weights and thresholds come from config.json under
"premarket_scoring" â€” nothing here is hard-coded strategy tuning.
é    )Ú
get_config)ÚvwapÚ
vwap_slopeÚrelative_volumeÚvolume_accelerationÚprice_momentumÚis_higher_highs_higher_lowsÚconsolidation_tightnessÚdistance_from_high_pctÚextension_from_vwap_pctÚ
spread_pctÚatrÚemaÚnormalized_slope_pctÚpullback_then_continuation)
ÚDEFAULT_CONFIGÚ_resistance_classificationÚ_breakout_in_progressÚ_extension_classificationÚ_momentum_stateÚRESISTANCE_SAFEÚRESISTANCE_CAUTIONÚEXTENSION_NORMALÚEXTENSION_EXTENDEDÚEXTENSION_SEVEREç        ç      Y@c                 C   s   t |t|| ƒƒS )N)ÚmaxÚmin)ÚxÚloÚhi© r"   ú	scorer.pyÚ_clamp1   s   r$   NÚsymbolÚbarsÚpm_highÚpm_lowÚavg_vol_baselineÚbidÚaskÚdaily_atr_pctÚreturnc           ?         sÎ  t ƒ d }|d }	|s| di ddidœS dd„ |D ƒ‰ d	d„ |D ƒ}
ˆ d
 }t|
ƒ}t|ƒ}t|t|d tdt|ƒd ƒƒd�}t||ƒ}t|
ƒ}t	tˆ ƒ|d kr[ˆ |d  d… nˆ ƒ}t
|tdt|ƒƒd�}t|ƒ}t||ƒ}t||ƒ}|r€|r€t||ƒnd}t|tdtdt|ƒd ƒƒd�}|rœ|rœ|| d nd}tˆ dƒ}tˆ ƒdkr¯ˆ dd… nˆ }t|ƒ}tˆ ƒdkrÌtˆ dd… ƒtˆ dd… ƒ }nd}t||ƒ}i t¥t ƒ  di ¡¥}i }t ƒ d d } t ƒ d d }!| |  krù|!k�rn n"| |! d }"|!|  d }#dt||" ƒ|#  }$|	d td|$ƒ |d< nd|d< |d  }%||%k�r<t||%d!  d"ƒ}&|	d# |& |d#< n|	d# ||%  d |d#< |d$ }'||'k�rct||'d%  d"ƒ}(|	d& |( |d&< n|'d'k�rr|	d& ||'  d nd|d&< |d( })||)k�r•t|d" d" d"ƒ}*|	d) t|*d'dƒ |d)< n|	d) t||) d'dƒ d* |d)< |d'k�r¶|	d+ t|d, d'dƒ nd|d+< t|| d-d.¡d/�}+|�rÎ|	d0 |d0< n|+�rÚ|	d0 d1 |d0< n|	d0 d2 |d0< ||k�rë|	d3 n|	d3 d4 |d3< |d'k�rü|	d5 n|	d5 d6 |d5< td7| d8 d'dƒ},|	d9 |, |d9< |�r.||k�r.|| | d }-t||||ƒ}.nd}-d:}.t|-||.|ƒ}/|/tk�rAd"}0n
|/tk�rId}0nd6}0|	d; |0 |d;< |d< }1t|1| |1 d'dƒ}2|	d= |2 |d=< tˆ ƒd%k�r–‡ fd>d„tdtˆ ƒƒD ƒ}3td?d@„ |3D ƒƒ}4|4t|3ƒ }5|	dA |5 |dA< n|	dA d |dA< |�r§|| | nd}6|�r²|| | nd}7t|6|7||ƒ}8|8tk�rÈ|	dB  |dB< n|8tk�r×|	dB  d* |dB< nd|dB< t|  ¡ ƒ}9tdCd@„ |	 !¡ D ƒƒ}:t|9|: d d'dDƒ};t ƒ d dE }<|du �p||<k}=||%k||'k|=||1k|8t"k||+|/||k|d'kdFœ
}>| t#|;dƒt#|9dƒdGdH„ | !¡ D ƒ|t#|dƒt#|dƒt#|dƒt#|dƒ|t#|dƒt#|dƒt#|dƒt#|dƒt#|dƒ|du�r`t#|dƒnddIœ|>dJœS )Ka(  
    bars: list of 1-min premarket bars, oldest first, each
          {"t","o","h","l","c","v"}
    pm_high / pm_low: premarket high/low so far
    avg_vol_baseline: 20-day average volume (or similar) used for RVOL
    bid/ask: latest quote, optional (affects spread/liquidity subscore)
    daily_atr_pct: [FEATURE 2026-09-15] this symbol's own trailing-30-day
        average daily true range, as a % of price -- from premarket_
        scanner.py's compute_volatility_baseline(). None (the default,
        for any caller that doesn't have it yet) means "unknown," which
        NEVER rejects a candidate -- only a confirmed-low reading does.
        This is a structural liquidity/character check (is this even
        capable of the kind of move a day trade needs), completely
        separate from today's intraday extension/resistance scoring
        above -- see config.json's universe.min_daily_atr_pct for the
        real-world BDC/SPAC cases (WHF, APXT) that motivated it.

    Returns: {"symbol", "total_score", "breakdown": {...}, "flags": {...}}
    Úpremarket_scoringÚweightsr   Úno_dataT)r%   Útotal_scoreÚ	breakdownÚflagsc                 S   ó   g | ]}|d  ‘qS )Úcr"   ©Ú.0Úbr"   r"   r#   Ú
<listcomp>Q   ó    z-score_premarket_candidate.<locals>.<listcomp>c                 S   r4   )Úvr"   r6   r"   r"   r#   r9   R   r:   éÿÿÿÿÚvwap_lookback_barsé   )ÚlookbackÚmomentum_lookback_snapshotsNé   g      à?é   é   )Úperiodr   é	   é   iúÿÿÿéýÿÿÿÚfast_predictionÚuniverseÚ	price_minÚ	price_maxÚprice_qualityg333333Ó?Úmin_premarket_volumeé   g      ð?Úvolume_qualityÚmin_rvolé   Úrvolr   Úacceleration_min_ratior   gš™™™™™Ù?r   g       @Úcoil_max_retrace_pctg      I@)Úmax_retrace_pctÚprice_structuregffffffæ?gš™™™™™É?Úvwap_structureg333333Ã?r   gš™™™™™¹?é   é   Úconsolidation_qualityFÚdistance_from_pm_highÚmax_spread_pctÚspread_liquidityc                    s    g | ]}ˆ | ˆ |d    ‘qS )r>   r"   )r7   Úi©Úclosesr"   r#   r9   ä   s     c                 s   s   � | ]	}|d krdV  qdS )r   r>   Nr"   )r7   Údr"   r"   r#   Ú	<genexpr>å   s   € z,score_premarket_candidate.<locals>.<genexpr>Úmomentum_consistencyÚextension_risk_penaltyc                 s   s    � | ]\}}|d kr|V  qdS )rd   Nr"   ©r7   Úkr;   r"   r"   r#   rb      s   € éd   Úmin_daily_atr_pct)
Úmeets_min_volumeÚmeets_min_rvolÚmeets_min_volatilityÚ	spread_okÚextendedÚhh_hl_structureÚcoiled_recoveryÚresistance_stateÚprice_above_vwapÚvwap_risingc                 S   s   i | ]
\}}|t |d ƒ“qS )rC   )Úroundre   r"   r"   r#   Ú
<dictcomp>  s    z-score_premarket_candidate.<locals>.<dictcomp>)Úpricer   r   rR   r   Úpremarket_volumeÚmomentum_pctÚconsolidation_tightness_pctÚdistance_from_pm_high_pctr   r   r,   )r%   r1   Ú	raw_scorer2   Úmetricsr3   )$r   Úsumr   r   r   r   Úlenr   r   r   r   r	   r
   r   r   r   r   r   r   Ú_FPE_DEFAULT_CONFIGÚgetÚabsr$   r   r   r   r   r   Úranger   r   r   ÚvaluesÚitemsr   rs   )?r%   r&   r'   r(   r)   r*   r+   r,   ÚcfgÚwÚvolumesÚcurrent_priceÚtotal_volumeÚv_wapÚv_sloperR   Ú	vol_accelÚmomentumÚhh_hlÚ	tightnessÚdist_from_highÚ	extensionÚsprÚatr_valÚatr_pct_valÚema9_valÚwindowÚslope_5m_proxyÚmomentum_accel_proxyÚmomentum_state_proxyÚfpe_cfgr2   rJ   rK   ÚmidÚ	band_halfÚ	closenessÚmin_volÚ	vol_ratiorP   Ú
rvol_ratioÚ	accel_minÚaccel_ratioro   Úconsolidation_scoreÚdist_to_resistanceÚbreakout_in_progressrp   Úresistance_scoreÚ
max_spreadÚspread_scoreÚdiffsÚsame_directionÚconsistencyÚprice_vs_ema9_atrÚvwap_distance_atrÚextension_stateÚtotalÚmax_possibleÚ
normalizedrh   rk   r3   r"   r_   r#   Úscore_premarket_candidate5   s
  
$
*

 
"


&
 *ÿ""ÿ

ÿ

öôír±   )r   r   )NNN)$Ú__doc__Úconfig_loaderr   Ú
indicatorsr   r   r   r   r   r   r	   r
   r   r   r   r   r   r   Úfast_prediction_enginer   r~   r   r   r   r   r   r   r   r   r   r$   ÚstrÚlistÚfloatÚdictr±   r"   r"   r"   r#   Ú<module>   s$    @0
þÿÿÿþþ