o
    *©›jù)  ã                
   @   s  d Z ddlmZmZ ddlmZ ddddddœZdZd	Zd
Z	dZ
eG dd„ dƒƒZeG d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ededefdd„Zdedededefd d!„Zdededed"edef
d#d$„Zd)d&eded"edefd'd(„Zd%S )*a¬  
structure_engine.py

[SIMULATION-ONLY 2026-09-05] Detects swing-point market structure (higher
highs / higher lows / lower highs / lower lows) and the failure patterns
built on top of it (structure break, failed higher low, support failure,
resistance rejection, failed breakout). Not consumed anywhere in the live
bot yet -- see config.json's structure_engine._note.

DIFFERENT FROM indicators.is_higher_highs_higher_lows() / is_lower_
highs_lower_lows() (already used by intraday_health.py and
entry_engine.py): those check whether a FIXED WINDOW of raw bars shows a
majority-higher (or majority-lower) step pattern -- useful as a quick
health/confirmation signal, but they don't identify discrete swing
PIVOTS the way this module does. This module finds actual local
turning points (a real "9.62 LOW, 9.81 HIGH, 9.70 HIGHER LOW, 9.94
HIGHER HIGH" sequence, per the project's original design brief) and
tracks the relationship between consecutive pivots of the same type.
Both modules stay -- they answer different questions at different
granularity, and neither is a duplicate of the other.

NO LOOK-AHEAD: a bar can only be confirmed as a swing pivot once enough
LATER bars exist to know price actually turned there (see
pivot_confirm_bars in config) -- there is an inherent, honest lag
between "a swing just formed" and "we can confirm it," same as any real
swing-trading system. This module never looks past whatever `bars` the
caller hands it.
é    )Ú	dataclassÚfield)Ú
get_configé   g333333Ó?é   )Úpivot_lookback_barsÚpivot_confirm_barsÚmin_swings_for_structureÚ"resistance_rejection_tolerance_pctÚstructure_score_lookback_swingsÚBULLISH_STRUCTUREÚBEARISH_STRUCTUREÚRANGE_STRUCTUREÚTRANSITION_STRUCTUREc                   @   s:   e Zd ZU eed< eed< eed< eed< dZeed< dS )Ú
SwingPointÚkindÚpriceÚindexÚtNÚlabel)	Ú__name__Ú
__module__Ú__qualname__ÚstrÚ__annotations__ÚfloatÚintÚobjectr   © r   r   úL/var/www/screener/trade/premarket_backup_2026-09-08_2010/structure_engine.pyr   0   s   
 r   c                   @   sˆ   e Zd ZU eed< eZeed< 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Zeed< e	e
d�Ze
ed< dS )ÚStructureReadingÚsymbolÚ	structureç        Úscore)Údefault_factoryÚswingsNÚlast_swing_highÚlast_swing_lowÚflagsFÚinsufficient_dataÚmissing)r   r   r   r   r   ÚSTRUCTURE_RANGEr"   r$   r   r   Úlistr&   r'   r(   Údictr)   r*   Úboolr+   r   r   r   r   r    9   s   
 r    ÚbarsÚlookbackÚconfirmÚreturnc              	      s  t | ƒ}g }t||| ƒD ]l}| || |… }| |d |d | … }| | d ‰ | | d ‰t‡ fdd„|D ƒƒrTt‡ fdd„|D ƒƒrT| tdˆ || | d d�¡ t‡fd	d„|D ƒƒryt‡fd	d„|D ƒƒry| td
ˆ|| | d d�¡ q|jdd„ d� t|ƒS )a£  
    Bar i is a swing HIGH if its high is >= every bar's high in the
    `lookback` bars before it AND every bar's high in the `confirm` bars
    after it (the "after" requirement is what makes this causal-safe --
    a pivot at index i literally cannot be identified until i+confirm
    bars have arrived). Mirror logic for swing LOWs. Ties (an exact
    repeat of the extreme) keep the EARLIEST bar as the pivot.
    é   ÚhÚlc                 3   s   � | ]	}ˆ |d  kV  qdS ©r5   Nr   ©Ú.0Úb)r5   r   r   Ú	<genexpr>W   ó   € z$find_swing_points.<locals>.<genexpr>ÚHIGHr   )r   r   r   r   c                 3   s   � | ]	}ˆ |d  kV  qdS )r6   Nr   r8   )r6   r   r   r;   Y   r<   ÚLOWc                 S   s   | j S ©N)r   )Úsr   r   r   Ú<lambda>\   s    z#find_swing_points.<locals>.<lambda>)Úkey)ÚlenÚrangeÚallÚappendr   ÚsortÚ_dedupe_adjacent)r0   r1   r2   Únr&   ÚiÚleftÚrightr   )r5   r6   r   Úfind_swing_pointsF   s   	,,€rM   r&   c                 C   sz   | sg S | d g}| dd… D ]+}|j |d j kr5|j dkr&|j|d jkn|j|d jk }|r4||d< q| |¡ q|S )zÿCollapses consecutive same-kind swings (e.g. two HIGH pivots in a
    row with no LOW in between -- can happen near the lookback/confirm
    boundary) down to the single most extreme one, so the sequence
    alternates HIGH/LOW the way a real zigzag does.r   r4   Néÿÿÿÿr=   )r   r   rF   )r&   Úoutr@   Úmore_extremer   r   r   rH   `   s   
*€rH   c                 C   sh   i }| D ]-}|  |j¡}|dur,|jdkr!|j|jkrdnd|_n|j|jkr)dnd|_|||j< q| S )zµAssigns HH/LH to HIGH swings and HL/LL to LOW swings, each
    relative to the PRIOR swing of the same kind (not the immediately
    preceding swing overall, which alternates kind).Nr=   ÚHHÚLHÚHLÚLL)Úgetr   r   r   )r&   Úlast_of_kindr@   Úprevr   r   r   Ú_label_swingsr   s   
rX   Ú
min_swingsc                 C   s    dd„ | D ƒ}dd„ | D ƒ}t |ƒ|d k s"t |ƒ|d k s"|r"|s'tddfS |d j}|d j}|dkr>|dkr>t||fS |d	krK|d
krKt||fS t||fS )a  Returns (structure, last_high_label, last_low_label) from the
    most recent labeled HIGH and LOW swing. Requires at least
    min_swings of EACH kind to have a real opinion -- otherwise RANGE
    (an honest 'not enough structure yet' default, not a guess).c                 S   ó$   g | ]}|j d kr|jdur|‘qS ©r=   N©r   r   ©r9   r@   r   r   r   Ú
<listcomp>‡   ó   $ z_classify.<locals>.<listcomp>c                 S   rZ   ©r>   Nr\   r]   r   r   r   r^   ˆ   r_   r4   NrN   rQ   rS   rR   rT   )rC   r,   r   ÚSTRUCTURE_BULLISHÚSTRUCTURE_BEARISHÚSTRUCTURE_TRANSITION)r&   rY   ÚhighsÚlowsÚlast_high_labelÚlast_low_labelr   r   r   Ú	_classify‚   s   (





rh   r"   Úlookback_swingsc                 C   sœ   dd„ | D ƒ| d… }|sdS |t krtdd„ |D ƒƒ}n%|tkr,tdd„ |D ƒƒ}ntdd„ |D ƒƒ}td	d„ |D ƒƒ}t||ƒ}t|t|ƒ d
 dƒS )a`  % of the last `lookback_swings` labeled swings that agree with
    the declared `structure` -- e.g. for BULLISH_STRUCTURE, an HH or HL
    agrees, an LH or LL disagrees. RANGE/TRANSITION score by internal
    consistency instead (how much the recent swings agree with EACH
    OTHER), since there's no single direction to measure agreement
    against.c                 S   s   g | ]	}|j d ur|‘qS r?   ©r   r]   r   r   r   r^   �   ó    z$_structure_score.<locals>.<listcomp>Nr#   c                 s   ó   � | ]
}|j d v rdV  qdS ©)rQ   rS   r4   Nrj   r]   r   r   r   r;   ¢   ó   € z#_structure_score.<locals>.<genexpr>c                 s   rl   ©)rR   rT   r4   Nrj   r]   r   r   r   r;   ¤   rn   c                 s   rl   rm   rj   r]   r   r   r   r;   ¦   rn   c                 s   rl   ro   rj   r]   r   r   r   r;   §   rn   ç      Y@r4   )ra   Úsumrb   ÚmaxÚroundrC   )r&   r"   ri   ÚlabeledÚagreeÚbullish_countÚbearish_countr   r   r   Ú_structure_score–   s   
rx   Úcfgc                    sp  ddddddœ}| s|S | d d }dd„ |D ƒ}dd„ |D ƒ}t |ƒdkrBt|d d… |d	 ƒ\}}	}	|ttfv rB||krBd
|d< |rV|d jdkrV||d jk rVd
|d< dd„ |D ƒ}
|
rj||
d jk rjd
|d< dd„ |D ƒ‰ ˆ r¶ˆ d j‰ˆ|d d  ‰‡ fdd„| D ƒ}t‡fdd„|D ƒƒ}|r |ˆk r d
|d< |S |s¶t‡‡fdd„|D ƒƒr¶|ˆk r¶d
|d< |S )NF)Ústructure_breakÚfailed_higher_lowÚfailed_breakoutÚsupport_failureÚresistance_rejectionrN   Úcc                 S   rZ   r[   r\   r]   r   r   r   r^   ¹   r_   z!_detect_flags.<locals>.<listcomp>c                 S   rZ   r`   r\   r]   r   r   r   r^   º   r_   r   r	   Trz   rS   r{   c                 S   ó   g | ]	}|j d kr|‘qS ©r>   ©r   r]   r   r   r   r^   Ì   rk   r}   c                 S   r€   ©r=   r‚   r]   r   r   r   r^   Ô   rk   r
   rp   c                    s"   g | ]}|d  ˆ d j kr|‘qS )r   rN   )r   r8   )Ú	all_highsr   r   r^   Ø   s   " c                 3   s   � | ]	}|d  ˆ kV  qdS )r   Nr   r8   )Ú
resistancer   r   r;   Ù   r<   z _detect_flags.<locals>.<genexpr>r|   c                 3   s    � | ]}|d  ˆ ˆ kV  qdS r7   r   r8   )r…   Ú	tolerancer   r   r;   Ü   s   € r~   )rC   rh   ra   rb   r   r   Úany)r0   r&   r"   ry   r)   Úcurrent_pricerd   re   Úprior_structureÚ_Úall_lowsÚ
bars_sinceÚbroke_abover   )r„   r…   r†   r   Ú_detect_flags­   sB   û 
ürŽ   Nr!   c              
   C   sø   i t ¥|ptƒ  di ¡¥}|d }|d }|| d }t|ƒ|k r1t| dd|› dt|ƒ› �gd�S t|||ƒ}t|ƒ}t||d	 ƒ\}}}	t|||d
 ƒ}
t	||||ƒ}dd„ |D ƒ}dd„ |D ƒ}t| ||
||rn|d j
nd |rv|d j
nd |dd�S )NÚstructure_enginer   r   r4   Tzneed at least z bars, have )r!   r*   r+   r	   r   c                 S   r€   rƒ   r‚   r]   r   r   r   r^   ó   rk   z%analyze_structure.<locals>.<listcomp>c                 S   r€   r�   r‚   r]   r   r   r   r^   ô   rk   rN   F)r!   r"   r$   r&   r'   r(   r)   r*   )ÚDEFAULT_CONFIGr   rU   rC   r    rM   rX   rh   rx   rŽ   r   )r!   r0   ry   r1   r2   Ú
min_neededr&   r"   rf   rg   r$   r)   Ú
last_highsÚ	last_lowsr   r   r   Úanalyze_structureã   s2   ÿør”   r?   )Ú__doc__Údataclassesr   r   Úconfig_loaderr   r�   ra   rb   r,   rc   r   r    r-   r   rM   rH   rX   Útuplerh   r   r   rx   r.   rŽ   r”   r   r   r   r   Ú<module>   s0    û 6