o
    êV¯j’  ã                   @   st   d Z ddlmZmZ dZdZ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 )aù  
exit_velocity_experimental.py

[2026-09-19] NOT wired into monitor.py's live loop -- backtest-only.
Built from the cross-symbol peak-pattern analysis run against the full
2026-09-18 session (all 30 candidates, not just the 9 the bot actually
traded): neither imbalance level at a stalled peak nor imbalance flipping
negative afterward showed any real discriminating power between the 161
failed-breakout events and the 275 that recovered (52-73% recovery,
non-monotonic, across every imbalance bucket tested) -- directly
undermining the assumption exit_prf_experimental.py and
exit_resistance_timer_experimental.py were both built on. The one signal
that DID show a clean, monotonic, statistically significant gradient
(ratio>=1.0 vs <1.0: 73.0% recovery n=204 vs 54.3% n=232, p~0.00005) was
the ratio of trade participation (buy+sell volume) in the 90s after a
stall vs the 90s before it -- rising participation predicts recovery,
fading participation predicts failure.

This does NOT explain GLOO itself (participation ratio 0.92, imbalance
+73% at its own real top -- looked completely healthy right up to the
reversal), so it is not being sold as "the fix." It's a different SHAPE
of signal than everything tried so far, per the explicit design goal
this was built to: PRF and the resistance-timer both wait a FIXED time
window (poll-count confirm reads, or a flat 60-120s clock) regardless of
how fast or slow price is actually giving back. This module instead
reacts to the RATE of the post-peak decline (a short trailing-window
price velocity, independent of how long the stall has been running) --
matching the one clear practitioner rule the accompanying web research
turned up for this exact discrimination problem: a fast snapback is a
failure, a move that holds/builds is a genuine pause -- gated by the
participation-ratio finding above so a merely-noisy fast wiggle with
volume already picking back up doesn't fire.

Single-read trigger once both conditions are true (no confirm-read
hysteresis layered on top, deliberately -- the whole point is reacting
faster than PRF/resistance-timer's wait-N-reads designs), using
stream.py's 30s sub-bars (get_bars_sub()) for both the velocity
reference price and the participation-ratio volumes, same buffer
fast_slope_exit.py already reads.
é    )Ú	dataclassÚfieldÚHOLDÚEXITg      @é   g      @é   g      ð?)Úmin_mfe_pctÚvelocity_lookback_bucketsÚ min_decline_velocity_pct_per_minÚparticipation_window_bucketsÚmax_participation_ratio_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 )ÚVelocityDecisionÚ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_velocity_experimental.pyr   8   s   
 r   c                 C   s   i t ¥| pi ¥S ©N)ÚDEFAULT_CONFIG)Úcfgr   r   r   Ú
_merge_cfgB   s   r#   Nr   ÚbarsÚsub_barsÚentry_priceÚstate_inr"   Úreturnc                 C   s´  t |ƒ}t|pi ƒ}t|d |d d ƒd }t|ƒdk s#t|ƒ|k r*t| d|d�S |d d	 }| d
|¡}t||ƒ}	||k}
|rI|	| | d nd}||d k}d
|	i}|t|	dƒt|dƒ||
dœ}|sxt| td|d›d|d › d�||d�S |
rˆt| td|d›d�||d�S |d|d   }|d	 }|d d }|r¤|| | d nd}|r®||d  nd}|d }tdd„ || d… D ƒƒ}t|ƒd| krÚtdd„ |d| | … D ƒƒnd}|dkrä|| nd}| 	t|dƒt|dƒt|dƒ|durýt|dƒndd œ¡ ||d! k}|du�o||d" k }|�r4|�r4t| t
d#d$|	d›d%|d&›d'|d(›d)|d›d*�	||d+�S t| td,|	d›d-|d&›d.|d! › d/|du�rL|nd0› d1|d" › d�||d�S )2a—  
    bars: same short rolling 1-min-bar window exit.py/PRF take.
    sub_bars: stream.SymbolBuffer.get_bars_sub() -- 30s-bucket bars,
        oldest first, each {"t","o","h","l","c","v",...}.
    state_in: caller-held state fed back every poll -- {"peak_price"}.
        Fresh {} at entry. No confirm-count/stage tracking needed --
        this is a memoryless single-read trigger past the peak itself.
    r	   r   é   r   r   zinsufficient sub-bar history)r   r   r   éÿÿÿÿÚcÚ
peak_priceg      Y@g        r   é   )Úpricer,   Úmfe_pctÚarmedÚmade_new_highznot armed yet (MFE z.2fz% < z%))r   r   r   r   r   zmaking new highs ($z.4fú)g      >@g      N@c                 s   ó   � | ]}|d  V  qdS ©ÚvNr   ©Ú.0Úbr   r   r   Ú	<genexpr>{   ó   € zevaluate.<locals>.<genexpr>Nc                 s   r3   r4   r   r6   r   r   r   r9   |   r:   éþÿÿÿr   )Úvelocity_pct_per_minÚ
recent_volÚ	prior_volÚparticipation_ratior
   r   Tzfast decline off peak $z: z.1fz%/min over z.0fzs, participation ratio z (not picking back up))r   r   r   r   r   r   z
off peak $z: velocity z%/min (need >=z), participation ratio zn/az (need <)r#   r   ÚmaxÚlenr   ÚgetÚroundr   ÚsumÚupdateÚ
STATE_EXIT)r   r$   r%   r&   r'   r"   Úneed_bucketsr.   Ú
prior_peakr,   r1   r/   r0   Ú
base_stater   Úref_barÚ	ref_priceÚlookback_secondsÚvelocity_pctr<   Úwr=   r>   r?   Údecline_fast_enoughÚparticipation_not_elevatedr   r   r   ÚevaluateF   s€   

ÿþþ4ý

ÿÿÿûÿþýúrQ   r    )Ú__doc__Údataclassesr   r   r   rF   r!   r   r#   r   ÚlistÚfloatr   rQ   r   r   r   r   Ú<module>   s*    )û		ÿÿÿÿ