o
    s¶j   ã                   @   s„   d Z ddlmZ ddlmZ ddlmZ ddlZedƒZdefdd	„Z	defd
d„Z
dd„ Z		ddededededef
dd„ZdS )aB  
universe.py

Builds the tradable symbol universe and does a cheap first-pass filter
before any bar data is pulled. Ported from screener/premarket's
premarket_scanner.py (get_universe_symbols / prefilter_by_snapshot),
which itself already implements the same universe filters the
breakout_bot design doc calls for (Part II section 2) -- price band,
liquidity floor, ETF/leveraged/name-complexity exclusion. Reimplemented
here rather than imported so this project has no runtime dependency on
screener/premarket.

Deliberately does NOT apply any RVOL/spread/volatility hard gate here
-- those belong to sip_bot's own scorer.py, not this project's
philosophy (see scanner.py's module docstring: candidate_score is a
pure continuous rank, with no hard reject gates beyond universe
membership and a minimum-bars data-availability check).
é    )Ú
get_config)Ú
get_logger)ÚdatetimeNÚuniverseÚreturnc              
   C   sJ   g }| D ]}|  |jt|jƒt|jƒt|jƒt|jƒt|jƒdœ¡ q|S )N)ÚtÚoÚhÚlÚcÚv)ÚappendÚ	timestampÚfloatÚopenÚhighÚlowÚcloseÚvolume)ÚbarsÚoutÚb© r   ú$/var/www/screener/trade1/universe.pyÚ_bars_to_dicts   s   
þr   c                    s€   t ƒ d }| d¡r|d S |  ¡ }t| dg ¡ƒ‰ ‡ fdd„|D ƒ}t|ƒ}tj|dd�}t d|t|ƒ › d	|› d
�¡ |S )z÷
    Pulls active, tradable US equities and applies:
      1. the include/exclude_symbols lists from config
      2. ETF/fund/trust exclusion
      3. leveraged/inverse ("multiplier") product exclusion
      4. company-name syllable-count cap
    r   Úinclude_only_symbolsÚexclude_symbolsc                    s&   g | ]}t |d dƒr|jˆ vr|‘qS )ÚtradableF)ÚgetattrÚsymbol)Ú.0Úa©Úexcludedr   r   Ú
<listcomp>5   s   & z(get_universe_symbols.<locals>.<listcomp>T)Úlog_rejectionszB[UNIVERSE] Symbol filters (ETF/leveraged/name-complexity) removed z of z candidates)	r   ÚgetÚget_tradable_assetsÚsetÚlenÚsymbol_filtersÚfilter_symbol_listÚlogÚinfo)ÚclientÚcfgÚassetsÚ
candidatesÚbeforeÚ	survivorsr   r"   r   Úget_universe_symbols&   s   


ÿ
ÿr4   c                 C   st   ddl m} t| ddƒ}t| ddƒ}|dur8t|ddƒdur8|tƒ d d ƒ}|j |¡ ¡ t |¡ ¡ k r8|S |S )	a/  [BUGFIX 2026-09-25] The last COMPLETE session's daily bar.
    Before the open, today's daily bar doesn't exist yet, so the snapshot's
    daily_bar IS the prior session and previous_daily_bar is two sessions
    back. Confirmed: every 9:28 opening scan (09-17..09-25) saved prev-day
    high/close/volume from two sessions back for 29-30 of 30 symbols (JAGX
    9/25: 'previous day high' 19.46 = 9/23's high, real 9/24 high 7.72;
    gap 1.1% instead of 44%). After the open, daily_bar is today's partial
    bar and previous_daily_bar is correct, as before.r   )ÚZoneInfoÚ	daily_barNÚprevious_daily_barr   ÚscheduleÚtimezone)	Úzoneinfor5   r   r   r   Ú
astimezoneÚdater   Únow)Úsnapr5   ÚdailyÚprevÚtzr   r   r   Ú_prior_session_bar=   s   	rB   ÚsymbolsÚbaseline_outÚprev_day_high_outÚprev_close_outc              
   C   s   t ƒ d }g }|  |¡}|s|S | ¡ D ]y\}}	zj|	j}
|
du r"W qt|
jƒ}|d |  kr5|d ks8n W qt|	ƒ}|durX|jrXt|jƒ}||d k rPW q|durX|||< |durj|durj|jrjt|jƒ||< |dur||dur||j	r|t|j	ƒ||< | 
|¡ W q ttfy�   Y qw |S )aV  
    Cheap first pass using snapshot data to cut the universe down before
    pulling minute bars for scoring. Filters on price band and a real
    prior-day volume floor.

    baseline_out: if a dict is passed, populated with {symbol: real
        prior-day volume} from snap.previous_daily_bar.volume (the
        prior COMPLETE session -- NOT snap.daily_bar, which is today's
        session-so-far and would make an RVOL calculation compare
        today's volume to itself).

    prev_day_high_out / prev_close_out: same pattern, populated with
        {symbol: previous session's high / close} from
        snap.previous_daily_bar. prev_close is a REQUIRED input to
        scanner.py's gap_pct -- a symbol missing it there gets skipped
        entirely, unlike prev_day_high which fails open (falls back to
        the next resistance level).
    r   NÚ	price_minÚ	price_maxÚmin_avg_daily_volume)r   Úget_snapshotsÚitemsÚlatest_trader   ÚpricerB   r   r   r   r   ÚAttributeErrorÚ	TypeError)r.   rC   rD   rE   rF   r/   r3   Ú	snapshotsr   r>   rL   rM   Úprev_barÚprev_volumer   r   r   Úprefilter_by_snapshotP   s:   



ÿrS   )NNN)Ú__doc__Úconfig_loaderr   Úlogger_setupr   r   r*   r,   Úlistr   r4   rB   ÚdictrS   r   r   r   r   Ú<module>   s$    
ÿÿÿÿ