o
    üå®jA2  ã                   @   s–   d Z ddlmZmZ ddlmZ dZdZdZdZ	dZ
d	Zd
Z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 ) an	  
exit_prf_experimental.py

[2026-09-19] NOT wired into monitor.py's live loop -- backtest-only.
Implements the "Price Response Failure" (PRF) concept worked out in the
2026-09-18 GLOO conversation (entered 4.93, peaked 5.58 at 10:58-11:00
ET on repeated +90-100% buy imbalance that never produced a new high,
imbalance then flipped negative for several reads, price broke down to
5.41/5.46 by 11:02 -- exit.py's three real layers stayed silent through
all of it; the trade didn't actually close until 18:17, round-tripping
back to 5.225. See also exit_giveback_experimental.py, built the same
day off the same trade to attack the same gap from a different angle
(peak-giveback voting instead of failed-breakout detection).

Core idea, straight from that conversation: the earliest tell isn't
negative imbalance by itself -- it's strong POSITIVE imbalance that
stops moving price. That's absorption at resistance, and it shows up
before the deterioration engine's price-structure signals do. A
four-stage, reversible state machine per symbol:

  NORMAL            -- price making new highs (or hasn't yet moved
                        min_mfe_pct since entry -- PRF doesn't evaluate
                        pre-move noise, same "after +3% MFE" gate the
                        conversation proposed).
  WATCH -> WARNING   -- price fails to exceed its own peak-since-entry
                        for resistance_confirm_reads consecutive polls
                        while trade imbalance stays >= strong_imbalance_
                        threshold (buyers hitting the market, price not
                        responding).
  STRONG_WARNING      -- imbalance then flips negative for
                        imbalance_reversal_reads consecutive polls,
                        still no new high.
  PROFIT_PROTECTION   -- structure also deteriorates (lower-highs/
                        lower-lows over the recent window -- reused from
                        exit.py's own deterioration check). Exits once
                        this holds for confirm_reads consecutive polls.

Reversible at every stage: any poll where price prints a new peak high
cancels the warning outright and returns to NORMAL, per the
conversation's explicit safeguard ("if they dip and recover, the
warning disappears") -- this is what should keep PRF from tripping on
ordinary chop the way a naive giveback-from-peak measure alone might.
é    )Ú	dataclassÚfield)Úis_lower_highs_lower_lowsÚNORMALÚWATCHÚWARNINGÚSTRONG_WARNINGÚPROFIT_PROTECTIONÚHOLDÚEXITg      @g333333Ã?gffffffæ?é   é   g      ø?)Úmin_mfe_pctÚresistance_band_pctÚstrong_imbalance_thresholdÚresistance_confirm_readsÚimbalance_reversal_readsÚstructure_lookback_barsÚconfirm_readsÚmin_giveback_pct_to_exitc                   @   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 )ÚPRFDecisionÚsymbolÚstateFÚshould_exitÚ Úreason)Údefault_factoryÚmetricsÚ	state_outN)Ú__name__Ú
__module__Ú__qualname__ÚstrÚ__annotations__Ú
STATE_HOLDr   r   Úboolr   r   Údictr   r   © r'   r'   ú0/var/www/screener/trade/exit_prf_experimental.pyr   U   s   
 r   c                 C   s   i t ¥| pi ¥S ©N)ÚDEFAULT_CONFIG)Úcfgr'   r'   r(   Ú
_merge_cfg_   s   r,   Nr   ÚbarsÚentry_priceÚstate_inr+   Úreturnc                 C   s&  t |ƒ}t|pi ƒ}t|ƒdk rt| d|d�S |d d }| d|¡}t||ƒ}||k}	| dt¡}
| dd	¡}| d
d	¡}| dd	¡}|rN|| | d nd}||d k}|t|dƒt|dƒ||	||
dœ}d|i}|sˆt| td|d›d|d › d�|i |¥td	d	d	dœ¥d�S |	r§t| td|d›d�i |¥dti¥i |¥td	d	d	dœ¥d�S |du rÃt| tdi |¥d|
i¥i |¥|
|||dœ¥d�S ||d|d d   k}||d k}| 	||d œ¡ |
tkr÷|rô|rô|d7 }||d! k rñt
nt}
n‚d	}n|
t
tfv �r'|�r|�r|d7 }||d! k�rt}
|d	k �r$|d7 }||d" k�r#t}
nRd	}nO|
tk�rO|d	k �r6|d7 }nd	}t|t|d# t|ƒƒd$�}||d%< |�rNt}
n'|
tk�rvt|t|d# t|ƒƒd$�}||d%< |�sm|d	k �rr|d7 }nd	}t}
|�r�|| | d nd}t|dƒ|d&< |
|d< i |¥|
|||dœ¥}|
tk�rì||d' k�rì||d( k�rÎt| td)d*|d›d+|› d,|› d-|› d.|d›d/�|i |¥dd	i¥d0�S t| td1|
› d2|› d3|d' › d4|d›d|d( › d5�||d�S t| td1|
› d6|d›d7|d8›d9|› d:|› d;|› d3|d' › d<|d›d/�||d�S )=a6  
    bars: same short rolling 1-min-bar window exit.py/smart_engine.py take.
    trade_imbalance: stream.py's get_trade_imbalance() live read for this
        symbol, same signal exit.py's imbalance-decline gate and
        smart_engine's Stage 3 both use -- None means a cold buffer this
        poll (fails open: holds the PRF stage steady, doesn't advance or
        reset it).
    state_in: caller-held state, fed back every poll -- {"peak_price",
        "stage", "resistance_count", "imbalance_reversal_count",
        "confirm_count"}. Fresh {} at entry.
    é   zinsufficient bar data)r   r   r   éÿÿÿÿÚcÚ
peak_priceÚstageÚresistance_countr   Úimbalance_reversal_countÚconfirm_countg      Y@g        r   r   )Úpricer4   Úmfe_pctÚarmedÚmade_new_highÚtrade_imbalanceÚstage_inznot armed yet (MFE z.2fz% < z%))r5   r6   r7   r8   )r   r   r   r   r   z
new high $z.4fz -- PRF warning cancelledÚ	stage_outNz,no imbalance data this poll -- holding stageé   r   r   )Úat_resistanceÚstrong_buyingr   r   r   )ÚlookbackÚlower_highs_lower_lowsÚgiveback_pctr   r   Tz,PRF confirmed reversal: failed breakout at $z (z( resistance reads), imbalance reversal (z. negative reads), structure deteriorating for z reads, giveback ú%)r   r   r   r   r   r   z
PRF stage z: confirmed (ú/z) but giveback z% -- holdingz: peak $z, imbalance z+.0%z, resistance reads z, reversal reads z
, confirm z, giveback )r,   r&   Úlenr   ÚgetÚmaxÚSTATE_NORMALÚroundr$   ÚupdateÚSTATE_WATCHÚSTATE_WARNINGÚSTATE_STRONG_WARNINGr   ÚminÚSTATE_PROFIT_PROTECTIONÚ
STATE_EXIT)r   r-   r.   r=   r/   r+   r9   Ú
prior_peakr4   r<   r5   r6   r7   r8   r:   r;   r   Ú
base_staterA   rB   ÚlhlrE   Ú	out_stater'   r'   r(   Úevaluatec   sø   
ý
ÿý
ÿý
þý
€


€


ÿ
ÿþýýúÿÿüÿÿþþþûrX   r)   )Ú__doc__Údataclassesr   r   Ú
indicatorsr   rK   rN   rO   rP   rR   r$   rS   r*   r   r,   r"   ÚlistÚfloatr&   rX   r'   r'   r'   r(   Ú<module>   s<    ,ø	ÿÿÿÿ