o
    _š j»   ã                
   @   sÌ   d Z ddlmZmZ ddlmZ ddlmZ ddlm	Z
mZ 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dedefdd„Zdedefdd„Z		d#deded edefd!d"„ZdS )$a  
compression_engine.py

[Phase 3 -- new] Detects range/volatility compression ("coiling") ahead of a
potential breakout, and scores how much directional pressure is building
during that compression. Pure function, no state, no I/O -- same contract as
every other engine in this pipeline (stream_features.py, trend_engine.py,
structure_engine.py): given a bars window plus already-computed features/
structure, returns a score. Nothing here fetches data or stores anything.

Two related but distinct scores:
  COMPRESSION_SCORE        -- how tight/quiet the recent range is, relative
                              to its own recent history (direction-agnostic:
                              a symbol coiling before a breakdown scores just
                              as high on this alone as one coiling before a
                              breakout).
  BREAKOUT_PRESSURE_SCORE  -- COMPRESSION_SCORE scaled up only when the
                              tightening is happening in a bullish context
                              (VWAP rising, higher lows holding, price still
                              near its highs, volume not collapsing). A quiet
                              range with deteriorating structure is NOT
                              "pressure building toward a breakout" -- it's
                              just quiet, and this keeps that distinction
                              instead of conflating the two.

Both `features` (stream_features.StreamFeatures) and `structure`
(structure_engine.StructureReading) are optional -- if the caller doesn't
have them yet, breakout_pressure_score degrades to compression_score with no
bonus (multiplier 1.0) rather than failing, consistent with this project's
"missing secondary confirmation reduces confidence, doesn't hard-block"
principle (see the project's own design brief section 38).

Not consumed anywhere in the live bot yet -- config.compression_engine
follows the same enabled:false / SIMULATION-ONLY convention every other
engine added to this project uses until it's wired in and backtested.
é    )Ú	dataclassÚfield)Úpstdev)Ú
get_config)ÚatrÚdistance_from_high_pcté   é   é   é   ©Úrange_contractionÚatr_contractionÚclose_to_close_contractionÚholding_near_highsÚvolume_not_collapsingg      @gffffffæ?g333333Ã?gš™™™™™É?g      ø?g       @)	Úlookback_barsÚweightsÚnear_high_max_distance_pctÚvolume_not_collapsing_min_ratioÚpressure_bonus_vwap_slopeÚpressure_bonus_higher_lowsÚpressure_bonus_near_resistanceÚpressure_max_multiplierÚ near_resistance_max_distance_pctc                   @   s‚   e Zd ZU 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ed	�Zeed
< dZeed< eed	�Zeed< dS )ÚCompressionReadingÚsymbolç        Úcompression_scoreÚbreakout_pressure_scoreNÚrange_contraction_ratioÚatr_contraction_ratioÚ close_to_close_contraction_ratio)Údefault_factoryÚ	breakdownFÚinsufficient_dataÚmissing)Ú__name__Ú
__module__Ú__qualname__ÚstrÚ__annotations__r   Úfloatr   r    r!   r"   r   Údictr$   r%   ÚboolÚlistr&   © r0   r0   ú1/var/www/screener/premarket/compression_engine.pyr   ?   s   
 r   ÚxÚreturnc                 C   s   t dtd| ƒƒS )Nr   ç      ð?©ÚmaxÚmin)r2   r0   r0   r1   Ú_clamp01L   s   r8   Úclosesc                    s   ‡ fdd„t dtˆ ƒƒD ƒS )Nc                    s<   g | ]}ˆ |d   rˆ | ˆ |d    ˆ |d    d ‘qS )é   ç      Y@r0   )Ú.0Úi©r9   r0   r1   Ú
<listcomp>Q   s    *ÿz _pct_changes.<locals>.<listcomp>r:   )ÚrangeÚlenr>   r0   r>   r1   Ú_pct_changesP   s   
ÿrB   Úbarsc                 C   sD   | sdS | d d pd}t dd„ | D ƒƒtdd„ | D ƒƒ | d S )	Nr   éÿÿÿÿÚcr4   c                 s   ó   � | ]}|d  V  qdS ©ÚhNr0   ©r<   Úbr0   r0   r1   Ú	<genexpr>Y   ó   € z_range_pct.<locals>.<genexpr>c                 s   rF   )ÚlNr0   rI   r0   r0   r1   rK   Y   rL   r;   r5   )rC   Úpricer0   r0   r1   Ú
_range_pctU   s   ,rO   Nr   Úcfgc               
   C   sš  i t ¥|ptƒ  di ¡¥}|d }d| d }t|ƒ|k r-t| dd|› dt|ƒ› �gd�S || d	… }|d
| | … }t|ƒ}	t|ƒ}
|
dkrM|	|
 nd}t|tdt|ƒd ƒd�}t|tdt|ƒd ƒd�}|dkrq|| nd}t|ƒdkr„tt	dd„ |D ƒƒƒnd}t|ƒdkr—tt	dd„ |D ƒƒƒnd}|dkr¡|| nd}|d d }t
|tdd„ |D ƒƒƒ}|rÄtdd„ |D ƒƒt|ƒ nd}|rÕtdd„ |D ƒƒt|ƒ nd}|dkrß|| nd}|d }|d td| ƒ |d td| ƒ |d td| ƒ |d t|d | |d  ƒ |d t||d  ƒ d œ}t| ¡ ƒ}ttt| ¡ ƒ| ƒd! dƒ}d}|d	u�rRt|d"d#ƒ�sR|j d$¡�pHddk�rR||d% 7 }|d	u�r t|d"d#ƒ�s d&d„ |jD ƒ}|�rw|d jd'k�rw||d( 7 }|j}|d	u�r |�r || | d! }d|  k�r˜|d) k�r n n||d* 7 }t||d+ ƒ}ttd!|| ƒdƒ}t| ||t|d,ƒt|d,ƒt|d,ƒd-d.„ | ¡ D ƒd#d/�S )0aý  
    bars: 1-min bars, oldest first (stream.StreamManager.get_bars()) -- needs
        at least 2*lookback_bars+1 to compare a "recent" window against the
        "earlier" window immediately preceding it.
    features: optional stream_features.StreamFeatures -- used only for the
        VWAP-slope bonus on breakout_pressure_score. Omit to skip that bonus.
    structure: optional structure_engine.StructureReading -- used only for
        the higher-lows / near-resistance bonuses. Omit to skip those.
    Úcompression_enginer   é   r:   Tzneed at least z bars, have )r   r%   r&   Néþÿÿÿr   r4   )Úperiodc                 S   ó   g | ]}|d  ‘qS ©rE   r0   rI   r0   r0   r1   r?   z   ó    z'analyze_compression.<locals>.<listcomp>r   c                 S   rU   rV   r0   rI   r0   r0   r1   r?   {   rW   rD   rE   c                 s   rF   rG   r0   rI   r0   r0   r1   rK      rL   z&analyze_compression.<locals>.<genexpr>c                 s   rF   ©ÚvNr0   rI   r0   r0   r1   rK   �   rL   c                 s   rF   rX   r0   rI   r0   r0   r1   rK   ‚   rL   r   r   r   r   r   r   r   r   r   r;   r%   FÚsloper   c                 S   s$   g | ]}|j d kr|jdur|‘qS )ÚLOWN)ÚkindÚlabel)r<   Úsr0   r0   r1   r?   œ   s   $ ÚHLr   r   r   r   é   c                 S   s   i | ]
\}}|t |d ƒ“qS )rR   )Úround)r<   ÚkrY   r0   r0   r1   Ú
<dictcomp>­   s    z'analyze_compression.<locals>.<dictcomp>)r   r   r   r    r!   r"   r$   r%   )ÚDEFAULT_CONFIGr   ÚgetrA   r   rO   Úcalc_atrr6   r   rB   r   Úsumr8   Úvaluesra   ÚgetattrÚvwapÚswingsr]   Úlast_swing_highr7   Úitems) r   rC   ÚfeaturesÚ	structurerP   ÚlookbackÚmin_barsÚrecentÚearlierÚrecent_rangeÚearlier_ranger    Ú
recent_atrÚearlier_atrr!   Ú
recent_stdÚearlier_stdr"   Úcurrent_priceÚdist_from_highÚrecent_vol_avgÚearlier_vol_avgÚvolume_ratioÚwr$   Úmax_possibler   Ú
multiplierÚlowsÚ	last_highÚroom_pctr   r0   r0   r1   Úanalyze_compression\   sx   ÿ&&""ÿ
ÿö ùr…   )NNN)Ú__doc__Údataclassesr   r   Ú
statisticsr   Úconfig_loaderr   Ú
indicatorsr   rf   r   rd   r   r,   r8   r/   rB   rO   r*   r-   r…   r0   r0   r0   r1   Ú<module>   s@    %ûñÿÿÿ