o
    y¯j  ã                   @   s†   d Z ddlmZmZ ddlmZ dZdZdZdZ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 )aà  
exit_resistance_timer_experimental.py

[2026-09-19] NOT wired into monitor.py's live loop -- backtest-only.
User's proposed design (2026-09-19), deliberately simpler than
exit_prf_experimental.py's multi-stage confirm-count state machine: once
price stops printing new highs, check whether trade imbalance is still
positive (buyers still active). If so, that's "at resistance" -- start a
fixed real-time clock (recovery_window_seconds). If price hasn't made a
new high by the time that clock runs out, exit. No confirm-read voting,
no structure (lower-highs/lower-lows) layer, no strong-imbalance floor --
just "resistance detected -> wait N seconds for a new high -> exit if
none came."

Time-boxed in wall-clock seconds (not poll counts), since poll cadence
isn't guaranteed constant (config's poll_interval_seconds=5 is the
target, but real logged poll times drift -- see simulate.py's
load_real_poll_times docstring). state_in carries the resistance-start
timestamp as an ISO string so evaluate() stays a pure function of its
inputs, same convention as exit_prf_experimental.py's state_in/state_out.

Reversible exactly like PRF: any poll that prints a fresh peak high
cancels the timer outright, no matter how much of the window had
elapsed.
é    )Ú	dataclassÚfield)ÚdatetimeÚNORMALÚAT_RESISTANCEÚHOLDÚEXITg      @g333333Ã?ç        g      N@)Úmin_mfe_pctÚresistance_band_pctÚresistance_imbalance_thresholdÚrecovery_window_secondsc                   @   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 )ÚResistanceTimerDecisionÚ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_resistance_timer_experimental.pyr   +   s   
 r   c                 C   s   i t ¥| pi ¥S ©N)ÚDEFAULT_CONFIG)Úcfgr   r   r    Ú
_merge_cfg5   s   r$   Nr   ÚbarsÚentry_priceÚnow_tsÚstate_inr#   Úreturnc                 C   sú  t |ƒ}t|pi ƒ}t|ƒdk rt| d|d�S |d d }| d|¡}t||ƒ}	||k}
| d¡}| d¡}|r@|	| | d	 nd
}||d k}|t|	dƒt|dƒ||
||dœ}d|	i}|sxt| td|d›d|d › d�|i |¥dddœ¥d�S |
rš|rƒd|d›d�nd|d›d�}t| t||i |¥dddœ¥d�S |du r²t| |r¤tntd|i |¥||dœ¥d�S ||d k}||	d|d d	   k}| 	||dœ¡ |du �r|rö|röt| td|	d›d |d!›d"|d# d$›d%�|i |¥| 
¡ |	dœ¥d�S t| td&|d!›d�|i |¥dddœ¥d�S |t |¡  ¡ }t|dƒ|d'< |�r&|| | d	 nd
}t|dƒ|d(< ||d# k�rZt| td)d*|d# d$›d+|d›d,|d$›d-|d›d�	|i |¥dddœ¥d.�S t| td|d›d/|d$›d0|d# d$›d1|d›d2�	|i |¥||dœ¥d�S )3a  
    bars: same short rolling 1-min-bar window exit.py/exit_prf_experimental.py take.
    trade_imbalance: stream.py's get_trade_imbalance() live read, None on a cold buffer
        (fails open -- holds whatever state was already in progress).
    now_ts: this poll's timestamp (UTC datetime) -- the recovery window is measured
        against this, not a poll count.
    state_in: caller-held state fed back every poll -- {"peak_price",
        "resistance_since" (ISO string or None), "peak_at_resistance"}. Fresh {} at entry.
    é   zinsufficient bar data)r   r   r   éÿÿÿÿÚcÚ
peak_priceÚresistance_sinceÚpeak_at_resistanceg      Y@r	   r
   é   )Úpricer-   Úmfe_pctÚarmedÚmade_new_highÚtrade_imbalanceÚresistance_since_inznot armed yet (MFE z.2fz% < z%)N)r.   r/   )r   r   r   r   r   z
new high $z.4fz -- resistance timer cancelledzmaking new highs ($ú)z&no imbalance data this poll -- holdingr   é   r   )Úimbalance_positiveÚat_resistance_bandzat resistance $z, imbalance z+.0%z) still positive, no new high -- starting r   z.0fzs recovery timerzIno new high but not at-resistance-with-positive-imbalance yet (imbalance Úelapsed_secondsÚgiveback_pctTzno recovery within zs of hitting resistance at $z (z's elapsed, still no new high, giveback )r   r   r   r   r   r   z, ú/z)s elapsed waiting for recovery, giveback ú%)r$   r   Úlenr   ÚgetÚmaxÚroundr   ÚSTATE_AT_RESISTANCEÚupdateÚ	isoformatr   ÚfromisoformatÚtotal_secondsÚ
STATE_EXIT)r   r%   r&   r5   r'   r(   r#   r1   Ú
prior_peakr-   r4   r.   r/   r2   r3   r   Ú
base_stater   r9   r:   Úelapsedr<   r   r   r    Úevaluate9   s°   


ýüÿÿþÿý
ÿÿûÿüÿÿþûÿ
ÿürL   r!   )Ú__doc__Údataclassesr   r   r   ÚSTATE_NORMALrC   r   rH   r"   r   r$   r   ÚlistÚfloatr   rL   r   r   r   r    Ú<module>   s2    ü	ÿÿÿÿÿ