o
    A%¸jÐ  ã                   @   s¢   d Z dddg d¢g d¢g d¢gddd	œZd
edefdd„Zddedefdd„Zded
efdd„Zdede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S ) a  
volatility.py

[2026-09-23] Per-symbol volatility class and the minimum entry-stop
distance that goes with it (user request: "recognize when a stock is
volatile from the ones that has a low volatility and adjust the minimum
stop distance accordingly"). Motivated by 9/23's quiet names getting
3-7c stops from 3 x 1-min ATR (BLMN, LTRX: 3c) -- inside the normal
post-entry dip every trade makes.

Volatility = daily ATR(14) as % of price, from the prior completed
sessions' daily bars (scanner.compute_range_20d_high already fetches
them; simulate.py fetches the same as of the replayed date). Carried
to the rule modules as view.levels["daily_atr"] (a price distance,
not a level -- every level consumer skips it).

Floor = max(volatility floor, spread_mult x current spread), where the
volatility floor is either
  style "k":     k x daily ATR
  style "tiers": a % of price by class (low/medium/high daily ATR%).
The stop is then min(stop from the caller's own rule, price - floor):
a floor only ever WIDENS a stop, never tightens one.
FÚtiersg333333Ã?)g      @g333333ã?Úlow)g      @ç      ð?Úmedium)g    eÍÍAg      ø?Úhighg       @r   )ÚenabledÚstyleÚkr   Úspread_multÚunknown_floor_pctÚcfgÚreturnc                 C   s   i t ¥| pi ¥S )N)ÚDEFAULT_MIN_STOP)r   © r   ú&/var/www/screener/trade1/volatility.pyÚ	merge_cfg$   s   r   é   Ú
daily_barsÚperiodc              
   C   sŒ   t | ƒdk rdS g }t| | dd… ƒD ]#\}}| t|d |d  t|d |d  ƒt|d |d  ƒƒ¡ q|| d… }t|ƒt |ƒ S )z�Wilder-free simple ATR over the last `period` true ranges of
    completed daily bars (oldest first). None if not enough history.é   Né   ÚhÚlÚc)ÚlenÚzipÚappendÚmaxÚabsÚsum)r   r   ÚtrsÚprevÚbr   r   r   Ú	daily_atr(   s   @r"   Úpricec                 C   sT   |r| sdS ||  d }|d D ]\}}}||k r||f  S q|d d d |fS )N)ÚunknownNç      Y@r   éÿÿÿÿr   r   )r#   Údatrr   ÚpctÚmax_pctÚ_floorÚnamer   r   r   Úclassify4   s   ÿr,   Úspreadc                    s”   t ˆ||ƒ\‰ }ˆ dkrˆ|d  d }n|d dkr"|d | }nt‡ ‡fdd„|d D ƒƒ}t||d	 |p8d
 ƒ}|ˆ |rFt|dƒdœS ddœS )z†Returns {"floor", "class", "daily_atr_pct"} -- floor is a price
    distance (dollars) the stop must be at least this far below price.r$   r
   r%   r   r   c                 3   s*   � | ]\}}}|ˆ krˆ| d  V  qdS )r%   Nr   )Ú.0ÚmÚfÚn©Úclsr#   r   r   Ú	<genexpr>G   s   €( zstop_floor.<locals>.<genexpr>r   r	   g        r   N)ÚfloorÚclassÚdaily_atr_pct)r,   Únextr   Úround)r#   r'   r-   r   r(   Ú	vol_floorr5   r   r2   r   Ú
stop_floor>   s   r;   c                 C   sœ   | dd… }t |ƒdk ri S tdd„ |D ƒƒtdd„ |D ƒƒttdd„ |D ƒƒt |ƒ dƒttd	d„ |D ƒƒt |ƒ dƒttd
d„ |D ƒƒt |ƒ dƒdœS )a]  [2026-09-25] User's 5-day reference frame, from the last 5 COMPLETED
    sessions (oldest first): extreme high/low (the ceiling/floor traders
    defend), average daily high/low, and the average daily range (the
    stock's typical one-day move). Keys are prefixed ref_ so every level
    consumer skips them unless setup_plan.five_day_levels is on.éûÿÿÿNr   c                 s   ó   � | ]}|d  V  qdS ©r   Nr   ©r.   r!   r   r   r   r4   V   ó   € z%five_day_reference.<locals>.<genexpr>c                 s   r=   ©r   Nr   r?   r   r   r   r4   W   r@   c                 s   r=   r>   r   r?   r   r   r   r4   X   r@   é   c                 s   r=   rA   r   r?   r   r   r   r4   Y   r@   c                 s   s    � | ]}|d  |d  V  qdS )r   r   Nr   r?   r   r   r   r4   Z   s   € )Úref_5d_highÚ
ref_5d_lowÚref_5d_avg_highÚref_5d_avg_lowÚref_5d_avg_range)r   r   Úminr9   r   )r   Úlast5r   r   r   Úfive_day_referenceL   s   ûrJ   Úkeyc                 C   s   | dko	|   d¡ S )z;Keys in a resistance-levels dict that are NOT price levels.r"   Úref_)Ú
startswith)rK   r   r   r   Úis_level_key^   s   rN   N)r   )Ú__doc__r   Údictr   ÚlistÚintr"   Úfloatr,   r;   rJ   ÚstrÚboolrN   r   r   r   r   Ú<module>   s    ù
