o
    ÐV´j3  ã                   @   s–   d Z ddlmZmZ ddlmZ ddlmZmZm	Z
 dZdZ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fdd„ZdS )ao  
exit_giveback_room.py

[2026-09-21] Wired into monitor.py's live loop -- checked BEFORE exit.py's
three layers, since this rule is meant to react faster and tighter than
trend/imbalance/hard-stop can.

Motivated by the 2026-09-21 peak-vs-exit analysis: 17 of 24 trades that
day gave back a real, meaningful chunk of a genuine peak run-up before
any of exit.py's existing layers fired -- none of VWAP/trend/imbalance
reference the position's OWN high since entry, only absolute/session-wide
readings (AGEN +5.39% peak run-up, BNC +2.55%, CRML +2.39%, etc.).

Rule, confirmed 2026-09-22:
  1. Track peak_price = max close since entry (same pattern as
     exit_giveback_experimental.py / exit_step_lock_experimental.py).
  2. Inactive until price actually starts sloping down -- reuses the
     same normalized_slope_pct + classify_slope ATR-scaled dead-zone
     exit.py's own trend layer uses, so "sloping down" means the same
     thing everywhere in this codebase. No confirm-read wait on this
     gate -- the moment the slope classifies "negative," this layer
     starts evaluating every poll (speed is the whole point here, same
     reasoning as exit_fast_slope_experimental.py).
  3. Also inactive until peak_gain_cents (peak_price - entry_price, in
     cents) has cleared min_peak_gain_cents (15 cents by default,
     config-adjustable) -- below that floor the position hasn't run up
     enough for a giveback rule to mean anything yet (ordinary
     spread/noise).
  4. Once both gates are open, the allowed giveback room in cents is
     peak_gain_cents * giveback_ratio (1/4 default since 2026-09-22,
     was 2/3; config-adjustable) -- e.g. 20c peak gain -> 5c room,
     40c -> 10c, scaling linearly with every new peak. Exits the instant
     price drops through peak_price - room, no separate confirmation
     wait -- same no-confirm-needed shape as exit.py's Layer 1 hard
     stop, deliberately, since this is meant to fire BEFORE exit.py's
     slower layers ever get a chance to.

Sits ALONGSIDE exit.py, not replacing it -- monitor.py checks this first
each poll and only falls through to exit.py's three layers when this one
holds.
é    )Ú	dataclassÚfield)Ú
get_config)Únormalized_slope_pctÚclassify_slopeÚatrÚHOLDÚEXITg      .@g      Ð?é   gš™™™™™©?é   ç        g      ð?T)	Úmin_peak_gain_centsÚgiveback_ratioÚslope_lookback_barsÚmin_flat_threshold_pctÚflat_threshold_atr_fractionÚ
atr_periodÚbreakeven_lock_centsÚbreakeven_buffer_centsÚrequire_slopec                   @   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 )ÚGivebackRoomDecisionÚsymbolÚstateFÚshould_exitÚ Úreason)Údefault_factoryÚmetricsÚ	state_outN)Ú__name__Ú
__module__Ú__qualname__ÚstrÚ__annotations__Ú
STATE_HOLDr   r   Úboolr   r   Údictr   r   © r'   r'   ú-/var/www/screener/trade/exit_giveback_room.pyr   A   s   
 r   c                 C   s   i t ¥| ptƒ  di ¡¥S )NÚgiveback_room)ÚDEFAULT_CONFIGr   Úget)Úcfgr'   r'   r(   Ú
_merge_cfgK   s   r-   Nr   ÚbarsÚentry_priceÚstate_inr,   Úreturnc                 C   s|  t |ƒ}t|pi ƒ}t|ƒdk rt| d|pd|id�S |d d }| d|¡}t||ƒ}t|t|d tdt|ƒd	 ƒƒd
�}|rF|| d nd}	dd„ ||d  d… D ƒ}
t|
ƒ}t|d |	|d  ƒ}t	||ƒ}|| d }|t
|dƒt
|dƒt
|dƒ|dœ}|d rµ||d krµ||d d  }||krµt| tdd|d›d|d d›d|d›d|d›�|d|id�S |d  rÐ|d!krÐt| td"|› d#|d$›d%�|d|id&�S ||d' k rìt| td(|d›d)|d' d›d*�|d|id&�S ||d+  }||d  }t
|dƒ|d,< t
|dƒ|d-< ||k�r.t| tdd.|d›d/|d›d0|d›d1|d›d2|d+ d$›d3�|d|id�S t| td4|d›d3�|d|id&�S )5z¤
    bars: same short rolling 1-min-bar window exit.py takes.
    state_in: caller-held state fed back every poll -- {"peak_price"}.
        Fresh {} at entry.
    é   zinsufficient bar dataÚ
peak_price)r   r   r   éÿÿÿÿÚcr   é   é   )Úperiodg      Y@r   c                 S   s   g | ]}|d  ‘qS )r5   r'   )Ú.0Úbr'   r'   r(   Ú
<listcomp>b   s    zevaluate.<locals>.<listcomp>r   Nr   r   r
   )Úpricer3   Úpeak_gain_centsÚ	slope_pctÚtrendr   r   Tzbreakeven lock: peak +z.1fzc >= z.0fz
c, price $z.4fz
 back to $)r   r   r   r   r   r   r   Únegativeznot sloping down yet (z, slope z.3fz%))r   r   r   r   r   r   zsloping down but peak gain zc below zc floorr   Ú
room_centsÚfloorz giveback room exhausted: price $z <= floor $z (zc room off zc peak gain, ratio ú)z+sloping down, within giveback room (floor $)r-   r&   Úlenr   r+   ÚmaxÚcalc_atrÚminr   r   ÚroundÚ
STATE_EXITr$   )r   r.   r/   r0   r,   r<   Ú
prior_peakr3   ÚaÚatr_pctÚclosesr>   Úflat_threshr?   r=   r   Úbe_floorrA   rB   r'   r'   r(   ÚevaluateO   s„   
ÿ
$

þÿÿüþ
ÿü
ÿÿþûýrP   )N)Ú__doc__Údataclassesr   r   Úconfig_loaderr   Ú
indicatorsr   r   r   rF   r$   rI   r*   r   r-   r"   ÚlistÚfloatr&   rP   r'   r'   r'   r(   Ú<module>   s(    *ö	(