o
    ì(»j‡1  ã                
   @   s  d Z ddlmZmZ ddlmZmZ dZi dd“dd“d	d
“dd“dd“dd“dd“dd“dd“dd“dd“dd“dd“dd“d d“d!d“Zd"d#„ Zd$d%„ Z	d&d'„ Z
d(ed)ed*ed+ed,ef
d-d.„Zd/d0„ Zd(ed)ed*ed+ed,ef
d1d2„Zd(ed)ed*ed,efd3d4„Zd5S )6aÕ  
exit_rules.py

EXIT rules for every open position, whichever entry module bought it
(position["setup"] says which one; position["plan"] carries what that
module attached at entry). Called by monitor.py every poll -- see
rules_api.py. The core still sells everything at 15:55.

[2026-09-27] HIGHER-LOW TRAIL -- "hold while the higher lows hold".
The big-winner study (reports/2026-09-27_big_winner_patterns.pdf) found
the day's big winners climb all afternoon in a staircase of higher lows,
while the old exits sold every trade by 11:30. So:

  1. Starting stop:
       initial_stop = "entry"  the entry module's own stop
       initial_stop = "swing"  the last swing low BEFORE the entry (more
                               room), but never more than
                               max_initial_risk_atr x daily ATR below the
                               entry price, and never above the entry stop
  2. Trail: build swing_minutes bars (default 5-min) from the 1-min bars.
     Each time a new swing low forms AFTER the entry (a bar whose low is
     below the bars on both sides) and it is higher than the current
     stop, the stop moves up to it (minus buffer_cents). Never down.
  3. Sell when:
       trigger = "close"  a finished 1-min bar CLOSES below the stop
                          (a quick wick through the stop doesn't count), or
                          price falls disaster_pct below the stop at any time
       trigger = "touch"  price trades at or below the stop

[2026-09-27] mode "step_r" -- STEPPED STOP, then ATR TRAIL (user's design):
  R = entry price - the entry's stop (the risk per share).
  1. Every time price rises another R above the entry, the stop rises by R
     (stop = original stop + k*R after k steps). After the first step the
     stop is at the entry price (breakeven).
  2. From breakeven on, the stop trails step_atr_mult x the 1-minute ATR
     (Wilder, step_atr_period finished 1-min bars) below the highest price
     since the entry -- and it never moves down.
  3. Sell the moment price touches the stop (step_trigger "touch"), or on a
     finished 1-min bar closing below it ("close").
  Option step_min_stop_pct (0 = off), e.g. 1.0: the starting stop is at
     least this % below the buy price (the entry's own stop if lower still);
     R is measured from it, so the steps scale with it too.
  Option step_reversal_breakeven_atr (0 = off), e.g. 2.0: at the FIRST
     REVERSAL (first finished 1-min bar after the buy, once price has been
     above the buy price, that closes >= 1 x 1-min ATR below the high since
     the buy), if that high was less than 2.0 x 1-min ATR above the buy
     price, the stop moves up to the buy price (if price is already below
     it, that sells right away).

[2026-09-28] mode "smooth" -- HOLD WHILE THE TREND STAYS SMOOTH (user request after
AGEN was sold twice at its smoothest point):
  1. Starting stop: the entry's stop, at least smooth_min_stop_pct below the buy
     price (like exit B). Always active; sells on a touch.
  2. Smoothness = R^2 of the last smooth_bars (30) finished 1-min closes, signed
     by the slope (a smooth DOWN move is negative).
  3. Once smoothness reaches smooth_arm (0.8) after the buy, the trade is armed.
  4. Armed: sell the first time smoothness falls below smooth_break (0.8 / 0.6 /
     0.4 tested) -- the steady climb has broken.

Settings: config.json "exit_rules" (defaults below); "mode" picks the rule
("higher_low" = the higher-low trail above, "step_r" = stepped stop,
"smooth" = hold while smooth).
é    )ÚdatetimeÚ	timedelta)ÚExitDecisionÚ
MarketViewÚexitÚmodeÚ
higher_lowÚsmooth_barsé   Ú
smooth_armgš™™™™™é?Úsmooth_breakg333333ã?Úsmooth_min_stop_pctg      ð?Ústep_atr_multg       @Ústep_atr_periodé   Ústep_triggerÚtouchÚstep_reversal_breakeven_atrç        Ústep_min_stop_pctÚswing_minutesé   Úinitial_stopÚentryÚmax_initial_risk_atrg      à?ÚtriggerÚcloseÚbuffer_centsÚdisaster_pctc                    s¤   | dd… }|s
g S |d d t dd� ‰ i }|D ]*}|d }|j|j|jˆ  ddd�}| |¡}|du r:|d nt||d ƒ||< q‡ ‡fd	d
„t| ¡ ƒD ƒS )z<Completed 1-min bars -> completed N-min bars [(start, low)].NéÿÿÿÿÚté   ©Úminutesr   )ÚminuteÚsecondÚmicrosecondÚlc                    s*   g | ]\}}|t ˆd � ˆ kr||f‘qS )r"   )r   ©Ú.0ÚsÚlo©Úlast_endr#   © ú(/var/www/screener/trade1/./exit_rules.pyÚ
<listcomp>f   s   * z_buckets.<locals>.<listcomp>)r   Úreplacer$   ÚgetÚminÚsortedÚitems)Úbarsr#   ÚdoneÚoutÚbr    Ústartr+   r.   r,   r/   Ú_bucketsZ   s   
$r;   c                    s    ‡ fdd„t dtˆ ƒd ƒD ƒS )zf(start, low) of each confirmed swing low: lower than the bar before,
    no higher than the bar after.c                    sL   g | ]"}ˆ | d  ˆ |d   d  k rˆ | d  ˆ |d   d  krˆ | ‘qS )r!   r.   )r)   Úi©Úbucketsr.   r/   r0   l   s    8ÿz_swing_lows.<locals>.<listcomp>r!   )ÚrangeÚlenr=   r.   r=   r/   Ú_swing_lowsi   s    rA   c                 C   s”   | dd… }t |ƒdk rdS d}t||dd… ƒD ].\}}t|d |d  t|d |d  ƒt|d |d  ƒƒ}|du r?|n||| |  }q|S )zDWilder ATR over finished 1-min bars (the last bar is still forming).Nr   é   r!   Úhr'   Úc)r@   ÚzipÚmaxÚabs)r6   Úperiodr7   ÚatrÚprevr9   Útrr.   r.   r/   Ú_atr_1mp   s   8rL   ÚviewÚpositionÚstaterD   Úreturnc              
   C   sF  |d |d }}|  d¡rt||d|d d   ƒ}|| }d|vr,|j|d|dd	� t|d
 | jƒ|d
< |dkrvt|d
 | | ƒ}||d krv||d< |||  }||d krv|d|› d|dkrednd|d › d�› d�|d< |d< t| jt|d ƒƒ}	|  d¡p†d}
|
rñ|	rñ|  d¡sñt| jƒdkrñ| jd }t	 
|d ¡jddd�}|d |krñ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�r|	�r|d
 |d" |	  }||d k�r|d#|d" › d$|	d%›d�|d< |d< |d }t|d&ƒ|d t|d&ƒ|d |	�r9t|	d&ƒnd |d
 d'œ}|d( d)k�rst| jƒdk�rT| jd nd }|d u�oq|d t	 
|d ¡jddd�k�oq|d |k }n| j|k}|�r’tdd*d+| jd,›d-|d,›d|d › d�|d.�S td/d0|d,›d|d › d�|d.�S )1NÚentry_priceÚ
stop_pricer   r!   ç      Y@Ústopr   ú
entry stop)rT   ÚstepsÚhighÚhowrW   rV   zstep ú (Ú	breakevenú+ÚRú)rX   r   r   Úreversal_seenrB   éþÿÿÿÚ
entry_time©r%   r&   r    Úbar_highrC   rD   Tzfirst reversal after only z.1fz ATR -> buy pricer   ztrail z	x1m-ATR (z.3fé   )rT   Ú	stop_fromr\   rV   Úatr_1mÚhigh_since_entryr   r   ÚSTOPúprice ú.2fú
 hit stop ©rO   ÚreasonÚmetricsÚHOLDúholding above )r2   r3   ÚupdaterF   ÚpriceÚintrL   r6   r@   r   Úfromisoformatr1   Úroundr   )rM   rN   rO   rD   r   Ústop0r\   rV   ÚnewrI   Úk_revÚ	last_doneÚt_buyÚrunÚtrailrT   ÚmÚhitr.   r.   r/   Ú_step_r|   s^   
8 
  &ÿ
ÿ
ÿ
&ÿ"r~   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 )zNR^2 of closes vs time, signed by the slope (+ = smooth rise, - = smooth fall).r   Nr!   rB   c                 3   ó   � | ]	}|ˆ  d  V  qdS ©rB   Nr.   )r)   Úk)Úxmr.   r/   Ú	<genexpr>³   ó   € z_smoothness.<locals>.<genexpr>c                 3   s$   � | ]\}}|ˆ  |ˆ  V  qd S )Nr.   ©r)   r�   Úy)r‚   Úymr.   r/   rƒ   ´   s   €" c                 3   r   r€   r.   )r)   r†   )r‡   r.   r/   rƒ   µ   r„   r   r   c                 3   s,   � | ]\}}|ˆˆ |ˆ    d  V  qdS r€   r.   r…   ©Úsloper‚   r‡   r.   r/   rƒ   ¸   s   €* r   )r@   Úsumr?   Ú	enumerate)ÚclosesÚnÚsxxÚsstÚssrr.   rˆ   r/   Ú_smoothness­   s    r‘   c                    sÔ  |d }d|vr|j t|d |d|d d   ƒdd d� |d }t |d	 ¡jd
d
d�‰ | jd d… }t|d ƒ}t|ƒ|krNtdd„ || d … D ƒƒnd }‡ fdd„|D ƒ}	|d uri|	ri||d krid|d< |d ur~|d d u ru|nt	|d |ƒ|d< t
|dƒ|d ur‹t
|dƒnd |d |d dœ}
| j|kr«tddd| jd›d|d›�|
d�S |d rË|d urË||d k rËtddd|d›d |d › d!�|
d�S td"|d rÓd#nd$d%|d urÛ|ntd&ƒd›d'|d›� |
d�S )(NrQ   rT   rR   r!   r   rS   F)rT   ÚarmedÚpeak_r2r`   r   ra   r   r	   c                 S   s   g | ]}|d  ‘qS )rD   r.   ©r)   r9   r.   r.   r/   r0   Å   s    z_smooth.<locals>.<listcomp>c                    s   g | ]
}|d  ˆ kr|‘qS )r    r.   r”   ©ry   r.   r/   r0   Æ   s    r   Tr’   r“   rc   é   )rT   Ú
smoothnessr’   Úpeak_smoothnessrg   rh   ri   rj   rk   r   ÚSMOOTH_BREAKzsmoothness z fell below z (trend broke)rn   zriding smooth trendzwaiting for a smooth trendz (smoothness Únanz), stop )rp   r3   r   rs   r1   r6   rr   r@   r‘   rF   rt   rq   r   Úfloat)rM   rN   rO   rD   r   rT   r7   r�   Úr2Ú	since_buyr|   r.   r•   r/   Ú_smooth¼   s:    ÿ,"ÿ
"ÿ"ÿÿrž   c              
      s²  i t ¥| jpi ¥}|d dkrt| |||ƒS |d dkr#t| |||ƒS |d ‰ t |d ¡‰|d d }tt| jt	|d ƒƒƒ}d	|vrˆ|d
 }d}|d dkr€| j
 d¡}|r`ˆ |d |  n|}	‡ ‡fdd„|D ƒ}
|
r€t|
d | |	ƒ}||k r€|d}}|j||dd� |D ]3\}}|ˆkr½|| |d	 kr½|| jk r½|| d|d›d| d¡› d�|d	< |d< |d  d7  < qŠ|d	 }t|dƒ|d |d dœ}|d  d!krò| j|krñtd"d#d$| jd›d%|d›d|d › d&�|d'�S nVt| jƒd(krþ| jd) nd }|�r)|d* ˆk�r)|d+ |k �r)td"d#d,|d+ d›d-|d›d|d › d&�|d'�S | j|d|d. d/   k�rHtd"d#d$| jd›d0|d›�|d'�S td1d2|d›d|d › d&�|d'�S )3Nr   Ústep_rÚsmoothrQ   r`   r   rS   r   rT   rR   rU   r   ÚswingÚ	daily_atrr   c                    s$   g | ]\}}|ˆk r|ˆ k r|‘qS r.   r.   r(   ©r   Út_entryr.   r/   r0   ç   s   $ zevaluate.<locals>.<listcomp>r   zswing low before entryr   )rT   rX   Úraisedzhigher low ri   rY   z%H:%MzZ)rX   r¥   r!   rc   )rT   rd   Útimes_raisedr   r   Trg   rh   z	 <= stop r]   rk   rB   r_   r    rD   z1-min close z < stop r   éd   z far below stop rn   ro   )ÚDEFAULTSÚcfgr~   rž   r   rs   rA   r;   r6   rr   Úlevelsr2   rF   rp   rq   Ústrftimert   r   r@   )rM   rN   rO   rD   ÚbufÚswingsrT   rX   rI   ÚfloorÚbeforeÚcandr*   r+   r|   rx   r.   r£   r/   ÚevaluateÖ   sT   
",€
&ÿÿ"$ÿ""r±   N)Ú__doc__r   r   Ú	rules_apir   r   ÚNAMEr¨   r;   rA   rL   Údictr~   r‘   rž   r±   r.   r.   r.   r/   Ú<module>   sX    ?ÿþýüûúùø	÷
öõôóòñð1