o
    Ì®j}  ã                   @   sž   d Z ddlmZmZ ddlmZmZmZmZm	Z	m
Z dZdZddddd	d
dddddœ
ZeG dd„ dƒƒZdd„ Z	ddededededededefdd„ZdS )an  
exit_giveback_experimental.py

[2026-09-18] NOT wired into monitor.py's live loop -- backtest-only,
built to test the giveback-from-peak validation sequence proposed after
the GLOO case (entered 4.93, peaked 5.58 at 10:58 ET, exit.py's three
existing layers never fired until price had already round-tripped back
to 5.225 at 14:17 -- giving back over half the peak gain, because none
of VWAP/trend/imbalance-decline reference the position's OWN high since
entry, only absolute/session-wide readings).

A fourth layer, meant to sit alongside (not replace) exit.py's existing
three: only activates once price has given back a meaningful chunk of
its own peak (ATR-scaled, so it doesn't trip on ordinary chop), then
requires a MAJORITY of four independent votes -- chosen specifically
because each one showed a real, early, comparatively clean signal in
the GLOO data where the existing signals stayed silent or too noisy:

  1. structure  -- price lost VWAP, or EMA9 crossed under EMA20
  2. demand     -- buy-side volume's own rolling regression slope is
                    negative AND sell-side volume isn't fading at least
                    as fast (i.e. real distribution, not just a quiet
                    stretch where nobody's trading either side)
  3. momentum   -- RSI(14) has faded meaningfully (15+ points) off its
                    own high-since-entry AND sits below 50
  4. structure2 -- lower-highs/lower-lows over the recent window
                    (reused from exit.py's own deterioration check)

sustained for `exit_confirm_reads` consecutive polls, same hysteresis
pattern as exit.py's existing deterioration layer.
é    )Ú	dataclassÚfield)ÚvwapÚemaÚrsiÚlinreg_slopeÚis_lower_highs_lower_lowsÚatrÚHOLDÚEXITg       @g      ø?é   é   g      .@g      I@é   é   é   )
Úgiveback_atr_multipleÚmin_giveback_pctÚvotes_requiredÚconfirm_readsÚ
rsi_periodÚrsi_fade_from_peakÚrsi_ceilingÚvol_history_lenÚvol_slope_min_samplesÚstructure_lookback_barsc                   @   s^   e Zd ZU eed< eZeed< dZeed< dZ	eed< e
ed�Zeed< e
ed�Zeed	< d
S )ÚGivebackDecisionÚsymbolÚstateFÚshould_exitÚ Úreason)Údefault_factoryÚmetricsÚ	state_outN)Ú__name__Ú
__module__Ú__qualname__ÚstrÚ__annotations__Ú
STATE_HOLDr   r   Úboolr    r   Údictr"   r#   © r,   r,   ú5/var/www/screener/trade/exit_giveback_experimental.pyr   6   s   
 r   c                 C   s   i t ¥| pi ¥S ©N)ÚDEFAULT_CONFIG)Úcfgr,   r,   r-   Ú
_merge_cfg@   s   r1   Nr   ÚbarsÚbuy_volÚsell_volÚstate_inr0   Úreturnc                  C   s~  t |ƒ}t|pi ƒ}t|ƒdk rt| d|d�S |d d }t|ƒ}dd„ |D ƒ}t|dƒ}	t|d	ƒ}
t|td
tdt|ƒd ƒƒd�}|rJ|| d nd}t	||d d�}t
|t|d t|ƒƒd�}t| d|¡|ƒ}t| d|¡|ƒ}t| dg ¡ƒ|g }t| dg ¡ƒ|g }||d  d… }||d  d… }|r¥|| | d nd}t|d |d | ƒ}||||dœ}|t|dƒt|dƒt|dƒt|dƒt|	dƒt|
dƒt|dƒt|dƒ|||dœ}||k rüt| td|d›d |d›d!�|i |¥d"d#i¥d$�S ||k �p|	|
k }d%}t|ƒ|d& k�r/t|ƒ}t|ƒ}|d#k �o ||k}t|dƒ|d'< t|dƒ|d(< ||d) k �o=|| |d* k}|}||||g}t|ƒ}|||||d+œ|d,< ||d- k �rrt| td|d›d.|› d/�|i |¥d"d#i¥d$�S | d"d#¡d }||d0 k �r t| td|d›d1|› d2|› d3|d0 › d4�	|i |¥d"|i¥d$�S t| td5d6|d›d7|d8›d9|› d:|› d;�	|i |¥d"d#i¥d<�S )=a%  
    bars: same short rolling 1-min-bar window exit.py/smart_engine.py take.
    buy_vol/sell_vol: THIS poll's raw classified volume over the same
        rolling window smart_engine's imbalance uses (not the ratio --
        stream.SymbolBuffer doesn't expose raw volumes, caller pulls
        them from buf._trade_log the same way get_trade_imbalance() does).
    state_in: caller-held state, fed back every poll -- {"peak_price",
        "peak_rsi", "buy_vol_hist": [...], "sell_vol_hist": [...],
        "confirm_count"}. Fresh {} at entry.
    r   zinsufficient bar data)r   r    r#   éÿÿÿÿÚcc                 S   s   g | ]}|d  ‘qS )r8   r,   )Ú.0Úbr,   r,   r-   Ú
<listcomp>X   s    zevaluate.<locals>.<listcomp>é	   é   r   é   é   )Úperiodg      Y@g        r   r   )ÚlookbackÚ
peak_priceÚpeak_rsiÚbuy_vol_histÚsell_vol_histr   Nr   r   )rB   rC   rD   rE   r   )ÚpricerB   Úgiveback_pctÚ	floor_pctr   Úema9Úema20r   rC   Úlhlr3   r4   z	giveback z.2fz% below z% floor -- not evaluatedÚconfirm_countr   )r   r   r    r"   r#   Fr   Úbuy_vol_slopeÚsell_vol_sloper   r   )Ú	structureÚdemandÚmomentumÚ
structure2ÚcountÚvotesr   z% but only z/4 votes agreer   z%, z/4 votes, confirming (ú/ú)Tzgiveback-from-peak: z% off $z.4fz peak, z/4 votes agreed for z reads)r   r   r   r    r"   r#   )r1   r+   Úlenr   r   r   Úcalc_atrÚminÚmaxr   r   ÚgetÚlistÚroundr)   r   ÚsumÚ
STATE_EXIT) r   r2   r3   r4   r5   r0   rF   Ú
vwap_valueÚclosesrI   rJ   ÚaÚatr_pctÚrsi_valrK   rB   rC   Úbuy_histÚ	sell_histrG   ÚfloorÚ
base_stater"   Úvote_structureÚvote_demandÚ	buy_slopeÚ
sell_slopeÚvote_momentumÚvote_structure2rT   Ún_votesrS   r,   r,   r-   ÚevaluateD   s�   

 ÿ
üþ
ÿý"ýÿÿürp   r.   )Ú__doc__Údataclassesr   r   Ú
indicatorsr   r   r   r   r   r	   rX   r)   r_   r/   r   r1   r'   r\   Úfloatr+   rp   r,   r,   r,   r-   Ú<module>   s6      ö	ÿÿÿÿ