o
    •ÀjÎ  ã                   @   sˆ   d Z ddlmZmZ ddlmZ ddddddddd	d
dddddœZeG dd„ dƒƒZdd„ Zdd„ Zdd„ Z	dd„ Z
ddefdd„ZdS )a“  
exit_v2.py -- [2026-09-29] Exits the user asked for on 9/29 ("add the smooth exit and add
the breakeven rule stop trail"), ported from trade1/exit_rules.py modes "step_r" and
"smooth" (tested there on fixed entries: 21 days smooth break 0.6 +$1,213, 0.8 +$970 vs
exit B +$407; step_r + reversal breakeven -$547 vs hold -$1,275 on the first entries).

One managed stop + one trend check, evaluated every poll:
  STOP TRAIL (trail: true)
    start   the entry's stop, at least min_stop_pct below the buy price; R = buy - start
    steps   every time price has risen another R above the buy, the stop rises by R
            (after the first step it sits at the buy price = BREAKEVEN)
    trail   from breakeven on, the stop also trails atr_mult x the 1-min ATR (Wilder,
            atr_period bars) under the high since the buy; it never moves down
    reversal breakeven (reversal_breakeven_atr > 0): at the first 1-min bar after the
            buy that closes >= 1 ATR under the high since the buy, if that high was
            < reversal_breakeven_atr x ATR above the buy, the stop moves to the buy price
    sells on a touch of the stop
  SMOOTH TREND (smooth: true)
    smoothness = R^2 of the last smooth_bars finished 1-min closes, signed by the slope;
    armed once it reaches smooth_arm after the buy; armed -> sell when it drops under
    smooth_break (the steady climb broke)
  CLIMAX (climax_exit: true) [2026-09-30, user-approved X1 from the playbook study]
    sell when a finished 1-min bar after the buy traded >= climax_volx x its 14-day normal
    volume for that minute AND made the day's high (2 years of scanner stocks: after such a
    bar a -1% move came first 46-48% of the time vs +1% first 33-39%, every period)
replace_j: true -> these are the ONLY exits besides the 15:55 sell-all (J's giveback,
stall, imbalance, weakening and hard-stop layers are skipped); false -> they run first,
then J's layers as before.
é    )Ú	dataclassÚfield)ÚdatetimeFTg      ð?g       @é   é   gš™™™™™é?g333333ã?g      $@)ÚenabledÚ	replace_jÚtrailÚmin_stop_pctÚatr_multÚ
atr_periodÚreversal_breakeven_atrÚsmoothÚsmooth_barsÚ
smooth_armÚsmooth_breakÚclimax_exitÚclimax_volxÚsmooth_need_below_vwapc                   @   sD   e Zd ZU dZeed< dZeed< dZeed< e	e
d�Ze
ed< d	S )
ÚExitV2DecisionFÚshould_exitÚHOLDÚstateÚ Úreason)Údefault_factoryÚmetricsN)Ú__name__Ú
__module__Ú__qualname__r   ÚboolÚ__annotations__r   Ústrr   r   Údictr   © r$   r$   ú"/var/www/screener/trade/exit_v2.pyr   3   s
   
 r   c                 C   sx   d }t | | dd … ƒD ].\}}t|d |d  t|d |d  ƒt|d |d  ƒƒ}|d u r1|n||| |  }q|S )Né   ÚhÚlÚc)ÚzipÚmaxÚabs)ÚdoneÚperiodÚatrÚprevÚbÚtrr$   r$   r%   Ú_atr_1m;   s
   8r3   c                    sÆ   t | ƒ}|dk r
d S |d d t| ƒ| ‰‰t‡fdd„t|ƒD ƒƒ}t‡‡fdd„t| ƒD ƒƒ| ‰ t‡fdd„| D ƒƒ}|dkrEd	S t‡ ‡‡fd
d„t| ƒD ƒƒ}d||  ˆ dkr`d S d S )Né   r&   é   c                 3   ó   � | ]	}|ˆ  d  V  qdS ©r5   Nr$   )Ú.0Úk)Úxmr$   r%   Ú	<genexpr>H   ó   € z_smoothness.<locals>.<genexpr>c                 3   s$   � | ]\}}|ˆ  |ˆ  V  qd S )Nr$   ©r8   r9   Úy)r:   Úymr$   r%   r;   I   s   €" c                 3   r6   r7   r$   )r8   r>   )r?   r$   r%   r;   J   r<   r   g        c                 3   s,   � | ]\}}|ˆˆ |ˆ    d  V  qdS r7   r$   r=   ©Úsloper:   r?   r$   r%   r;   M   s   €* éÿÿÿÿ)ÚlenÚsumÚrangeÚ	enumerate)ÚclosesÚnÚsxxÚsstÚssrr$   r@   r%   Ú_smoothnessC   s    rL   c                 C   s2   ddl m} |  |dƒ¡}|jd d |j d S )Nr   )ÚZoneInfozAmerica/New_Yorké	   é<   r   )ÚzoneinforM   Ú
astimezoneÚhourÚminute)ÚtsrM   Útr$   r$   r%   Ú_minuteQ   s   rV   c                 C   s<   t dd„ | D ƒƒ}t dd„ | D ƒƒ}|r|| S | d d S )Nc                 s   s4   � | ]}|d  |d  |d  d |d  V  qdS )r'   r(   r)   é   ÚvNr$   ©r8   r1   r$   r$   r%   r;   X   s   €2 z_vwap.<locals>.<genexpr>c                 s   ó   � | ]}|d  V  qdS )rX   Nr$   rY   r$   r$   r%   r;   Y   ó   € rB   r)   )rD   )ÚbarsÚpvrX   r$   r$   r%   Ú_vwapW   s   r^   NÚreturnc                 C   sž  i t ¥|pi ¥}| d d }|d }| dd… }t |d ¡jddd�}	d|vrEt|d	 |d
|d d   ƒ}
|j|
|
d|dddd|d�	 t|d |ƒ|d< ||d  }t|ƒdkrnt|t	|d ƒd  d… t	|d ƒƒnd}|d �rE|dkr°t	|d | | ƒ}||d kr°||d< |d ||  }||d kr°||d
kr¢dnd|d
 › d�|d< |d< | 
d¡p¶d}|�r|�r|d �s|�r|d d |	k�r|d }t|d |d ƒ|d< |d |k�r|d |d | k�rd |d< |d | }||| k �r||d k�r|d!|| d"›d#�|d< |d< |d d
k�rE|�rE|d |d$ |  }||d k�rE|d%|d$ › d&|d'›d(�|d< |d< d}|d) �ršt	|d* ƒ}t|ƒ|k�rgtd+d,„ || d… D ƒƒ}|du�rƒ|�rƒ|d d |	k�rƒ||d- k�rƒd |d.< |du�rš|d/ du �r‘|nt|d/ |ƒ|d/< d}| 
d0¡�rù|�rùt|ƒdk�rù|d d |	k�rù|d }t|d ƒ}d|  k�rËd1k �rùn n,|d2 t|d3 | d
ƒ }||d4 k�rù|d td5d6„ |dd… D ƒƒk�rù||d f}|d }t|d7ƒ|d t|d7ƒ|d |�rt|d7ƒnd|d |du�r!t|dƒnd|d. |d/ du�r3t|d/ dƒndd8œ	}|�rWt|d d
ƒ|d4< td d9d:|d d;›d<|d
 d=›d>�|ƒS ||k�rqtd d?d@|d=›dA|d=›dB|d › d(�|ƒS |d) �r¥|d. �r¥|du�r¥||dC k �r¥| 
dD¡�r”|t| ƒk�s¥td dEdF|d=›dG|dC › dH�|ƒS tddIdJ|d=›dB|d › d(�|du�rÊdK|d=›|d. �rÃdLndM› � |ƒS dM |ƒS )Nz�bars: today's 1-min bars incl. the forming one; position: {"entry_price",
    "stop_price", "entry_time" (iso)}; state: per-position dict kept by the caller.rB   r)   Úentry_priceNÚ
entry_timer   )ÚsecondÚmicrosecondÚstopÚ
stop_pricer&   r
   g      Y@z
entry stopF)	Ústop0rd   ÚstepsÚhighÚhowÚarmedÚpeak_r2Úreversal_seenÚbar_highrh   rf   r5   r   rW   r	   rg   zbreakeven (+1R)zstep +ÚRri   r   rl   rU   rm   r'   Tzfirst reversal after only z.1fz ATR -> buy pricer   ztrail z	x1m-ATR (z.3fú)r   r   c                 S   s   g | ]}|d  ‘qS )r)   r$   rY   r$   r$   r%   Ú
<listcomp>‡   s    zevaluate.<locals>.<listcomp>r   rj   rk   r   i†  rX   Úvol_per_minr   c                 s   rZ   )r'   Nr$   )r8   Úxr$   r$   r%   r;   –   r[   zevaluate.<locals>.<genexpr>é   )	rd   Ú	stop_fromrn   rg   Úatr_1mÚhigh_since_entryÚ
smoothnessrj   Úpeak_smoothnessÚCLIMAXzexit_v2 climax: z.0fz"x normal volume on a new day high z.2fz (X1)ÚSTOPzexit_v2 stop: price z <= z (r   r   ÚSMOOTH_BREAKz!exit_v2 smooth break: smoothness z < z (trend broke)r   zexit_v2 hold: stop z, smoothness z armedr   )ÚDEFAULTSr   ÚfromisoformatÚreplaceÚminÚupdater+   rC   r3   ÚintÚgetrL   rV   Úroundr   r^   )r\   Úpositionr   ÚcfgÚrefr)   ÚpriceÚentryr-   Út_buyrf   rn   r/   rg   ÚnewÚk_revÚlastÚrunr	   Úr2rH   Úclimaxr1   ÚmiÚvolxrd   Úmr$   r$   r%   Úevaluate]   s¤   ÿ8
*.$ &
0
$22ýÿÿ
*,ÿÿ&ÿÿÿÿr“   )NN)Ú__doc__Údataclassesr   r   r   r|   r   r3   rL   rV   r^   r“   r$   r$   r$   r%   Ú<module>   s2    ò