o
    wÞ¶j³P  ã                   @   sž  d Z ddlmZmZ ddlmZ ddlmZ ddlmZ ddl	m
Z
mZmZ ddlmZ ddlZd	Zd
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'“d(d)“d'd'd*dd+œd'd,d-d.d/d-d-d0d1œd2œ¥ZeG d3d4„ d4ƒƒZd5d6„ Zd7ed8ed9ed:ed;ed<ed=efd>d?„Zd@edAed=efdBdC„ZdDedEed=efdFdG„Z	dQdHed@edIedJedKedLedMedNed=efdOdP„ZdS )Raê  
setup_analyzer.py

[2026-09-23] Per-tick trade-plan engine, built from the user's request to
have the bot do the same read Claude did by hand on AMPL at 14:57 ET
(steady uptrend with rising lows, above VWAP, but pressing a 3-hour
$13.70 ceiling with only 9c to the prior-day high and ~1h left -- "pass
now; buy a volume breakout above $13.72 or a pullback that holds
$13.63"). Replaces smart_engine's Stage 3 (imbalance) + entry_score
final gate when smart_engine.use_setup_plan is on; its stop replaces the
3x 1-min ATR stop.

Recomputed from scratch every call -- nothing cached -- so the plan's
levels, stop, target and triggers move with the chart:

  levels   VWAP, high of day, premarket high, prior-day high, 20-day
           high, and the current ceiling (exit_resistance_stall's
           cluster finder: >=3 one-minute highs within max(2c, 0.3%)).
  lows     pivot lows (a completed bar's low that is the lowest within
           +/- pivot_bars); "rising lows" = the last 3 strictly
           increasing. The last pivot low under price anchors the stop.
  stop     min(last pivot low - buffer, price - floor), floor =
           max(min_stop_pct of price, 2x spread) -- placeholder until
           the volatility-scaled floor is chosen from backtests.
  target   nearest level above the ceiling / above price; with nothing
           above (clear air), price + the typical 60-minute move.
  R:R      (target - price) / (price - stop), must be >= min_reward_risk.
  time     target distance must fit in what the stock typically moves
           in the minutes left before force-liquidation.
  volume   avg shares/min must clear min_shares_per_min; a breakout bar
           needs >= breakout_volume_ratio x average volume.

Plan states: PASS (with reasons), WATCH (breakout and/or pullback
triggers pending), BUY (a trigger fired and every check passed).
BUY triggers:
  breakout  one of the last 2 completed bars closed above a level
            (the ceiling or a key level it was below the bar before) by
            breakout_buffer on >= breakout_volume_ratio x avg volume.
  pullback  rising lows intact, price inside [last pivot low, +zone],
            and the last completed bar closed green with price holding
            at/above its close (turning back up).
é    )Ú	dataclassÚfield)Údatetime)Úmedian)Ú
get_config)ÚvwapÚatrÚnormalized_slope_pct)Ú_find_ceilingNÚPASSÚWATCHÚBUYÚmin_reward_riskg      ø?Ú
pivot_barsé   Úceiling_lookback_barséx   Úceiling_min_touchesé   Ú
band_centsg       @Úband_pctg333333Ó?Úbreakout_buffer_centsg      ð?Úbreakout_volume_ratioÚstop_buffer_centsÚmin_stop_pctç      à?Úmin_shares_per_miniô  Úpullback_zone_fractionç      Ð?Úpullback_zone_min_centsÚtime_fit_factorÚbreakout_stopÚpivotÚreal_level_targetsFÚceiling_target_min_age_miné   g      @)ÚenabledÚmax_atr_distanceÚrealistic_target_atré   Tgffffffæ?g333333Ó¿é   )r&   Úbreakout_slope_barsÚpullback_require_reclaimÚpullback_max_sell_volume_ratioÚpullback_min_imbalanceÚpullback_require_supportÚpullback_exempt_lower_highsÚswing_lookback_bars)Úreclaim_breakoutsÚfive_day_levelsÚcontext_rulesc                   @   s¤   e Zd ZU eed< dZeed< dZeed< d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< eed	�Zeed< defdd„Zdefdd„ZdS )ÚPlanÚstateÚ ÚsetupNÚstopÚtargetÚtarget_labelÚreward_risk)Údefault_factoryÚtriggersÚreasonsÚmetricsÚreturnc                 C   s~   | j | jrd| j› d�nd g}| jd ur2| jd ur2| d| jd›d| jd›d| j› d| jd›�¡ || j| j 7 }d 	|¡S )	Nz (ú)r7   zstop ú.2fz	, target z), R:R z; )
r6   r8   r9   r:   Úappendr;   r<   r?   r>   Újoin)ÚselfÚparts© rH   ú)/var/www/screener/trade/setup_analyzer.pyÚsummary   s    "ÿ
zPlan.summaryc              	   C   s,   | j | j| j| j| j| j| j| jdœ| j¥S )N)r6   r8   r9   r:   r;   r<   r>   r?   )	r6   r8   r9   r:   r;   r<   r>   r?   r@   )rF   rH   rH   rI   Úto_dict‡   s   þþzPlan.to_dict)Ú__name__Ú
__module__Ú__qualname__ÚstrÚ__annotations__r8   r9   Úfloatr:   r;   r<   r   Úlistr>   r?   Údictr@   rJ   rK   rH   rH   rH   rI   r5   s   s   
 r5   c                 C   sV   i t ¥| ptƒ  di ¡¥}i t d ¥| di ¡¥|d< i t d ¥| di ¡¥|d< |S )NÚ
setup_planr4   r3   )ÚDEFAULT_CONFIGr   Úget)ÚcfgÚbaserH   rH   rI   Ú
_merge_cfg�   s   rY   Ú	completedÚpriceÚvÚkeyÚbandÚctxrA   c                    sF  | |d  d d… ‰t ˆƒdk rddgi fS ttt ˆƒƒ‡fdd„d	�}|d
kr7tt|d ƒ‡fdd„d	�nd
}ˆ||d … ˆ|d d… }	}
g i }}|
sWddg|fS tdd„ |	D ƒƒt |	ƒ }dd„ |
D ƒ}|r~|r~tdd„ |D ƒƒt |ƒ | nd}t|dƒ|d< ||d krœ| d|d›d|d › d�¡ |dur·t|dƒ|d< ||d k r·| d|d›d�¡ |d  rðtd!d„ |
D ƒƒ}|g‡ fd"d„| ¡ D ƒ }t|ƒ}t|d#ƒ|d$< ||| k rð| d%|d›d&|d›�¡ |d' �rt | ƒdk�r| d d( | d) d* k}||d+< |�s| d,| d) d* d›d�¡ | ||fS )-zœChecks the user's pullback conditions beyond 'higher low intact' and
    'R:R acceptable' (already in analyze). Returns (ok, [failed reasons],
    details).r1   é   éÿÿÿÿr   Fz$not enough bars for pullback contextc                    ó   ˆ |  d S )NÚhrH   ©Úi©ÚwinrH   rI   Ú<lambda>Ÿ   ó    z#_pullback_context.<locals>.<lambda>)r]   r   c                    rb   )NÚlrH   rd   rf   rH   rI   rh       ri   Nz)no pullback yet (still at the swing high)c                 s   ó   � | ]}|d  V  qdS ©r\   NrH   ©Ú.0ÚbrH   rH   rI   Ú	<genexpr>¥   ó   € z$_pullback_context.<locals>.<genexpr>c                 S   s    g | ]}|d  |d k r|‘qS )ÚcÚorH   rm   rH   rH   rI   Ú
<listcomp>¦   s     z%_pullback_context.<locals>.<listcomp>c                 s   rk   rl   rH   rm   rH   rH   rI   rp   §   rq   ç        r   Úsell_volume_ratior-   z#selling not fading: red-bar volume rC   zx the advance (max rB   Ú	imbalancer.   z
imbalance z+.0%z still heavy sellingr/   c                 s   rk   ©rj   NrH   rm   rH   rH   rI   rp   ±   rq   c                    s$   g | ]\}}|d kr|ˆ k r|‘qS )Úhigh_of_dayrH   ©rn   ÚkÚlv©r[   rH   rI   rt   ²   ó   $ é   Úsupportzpullback low z broke support r,   rr   éþÿÿÿrc   Úreclaimz no reclaim yet (needs a close > )ÚlenÚmaxÚrangeÚminÚsumÚroundrD   Úitems)rZ   r[   r\   r]   r^   Útrade_imbalancer_   Úhi_iÚlo_iÚadvanceÚpullÚfailsÚdÚadv_volÚredÚ
sell_ratioÚpull_lowÚsupportsr€   r‚   rH   )r[   rg   rI   Ú_pullback_context”   sD   ("
*
ÿr–   Úbarsr{   c                    sŒ   g }t |tˆ ƒ| ƒD ]8}ˆ | d ‰t‡ ‡fdd„t || || d ƒD ƒƒrC|r<|d d ˆks<||d d  |krC| |ˆf¡ q|S )Nrj   c                 3   s    � | ]}ˆˆ | d  kV  qdS rx   rH   )rn   Új©r—   ÚlorH   rI   rp   Ã   s   € z_pivot_lows.<locals>.<genexpr>r`   ra   r   )r…   rƒ   ÚallrD   )r—   r{   Úoutre   rH   r™   rI   Ú_pivot_lows¿   s   *(€r�   ÚclosesÚspanc                    s.   ‡ ‡fdd„t ˆtˆ ƒƒD ƒ}|rt|ƒS dS )Nc                    s$   g | ]}t ˆ | ˆ |ˆ   ƒ‘qS rH   )Úabs)rn   re   ©rž   rŸ   rH   rI   rt   Ê   r~   z!_typical_move.<locals>.<listcomp>ru   )r…   rƒ   r   )rž   rŸ   ÚmovesrH   r¡   rI   Ú_typical_moveÉ   s   r£   ÚsymbolÚlevelsÚquoteÚnowrW   Ú
stop_floorrŠ   c           P         sŽ  t |ƒ}tƒ d }|dd… ‰tˆƒdk rttdgd�S |d d ‰t|ƒ}	t|tdt|ƒd	 ƒd
�}
|rG| d¡rG| d¡rG|d |d  nd}dd„ ˆdd… D ƒ}t	|ƒt|ƒ }t	dd„ ˆdd… D ƒƒtdtˆƒƒ }dd„ |D ƒ}t
|d d ˆ|d  d ƒ}|d d ‰ ddlm} ||d ƒ}| |¡}dd„ |d  d¡D ƒ\}}}t
d|j|||dd �|  ¡ d! ƒ}t
d	tdt|ƒd" ƒƒ}t||ƒ}|rÝ||| d#  |d$  nd}tˆ|d% ƒ}‡fd&d„|D ƒ}|r÷|d d	 nd}d'd„ |d(d… D ƒ}t|ƒd)k�o|d |d	   k �o|d" k n  }ˆ|d*  d… } t| ||d+ ƒ\}!}"}#t
d,d„ |D ƒƒ}$d-d.„ |�pBi  ¡ D ƒ}%|$|%d/< |!du�rT|!|%d0< t|%ƒ}&|d1 �r‹|& d/d¡ |!du�r‹|#d d2 }'t|'d3ƒ�r|||'  ¡ d! nd}(|(|d4 k �r‹|& d0d¡ |du�r’|nt
ˆ|d5  d d"| ƒ})ˆ|) g}*|du�r´|* ||d6 d  ¡ t|*ƒ}+ˆ|+ },|d7 }-|�pÄi }.|. d8¡}/|. d9¡|. d:¡}0}1|�rÞ|d d; nd}2i }3|0�r3|1�r3|0|1k�r3tˆ|1 |0|1  d< d	ƒt|0ˆ d< d	ƒ|/�rt|0ˆ |/ d"ƒnd|0|1d=œ}3|-d> �r3|/�r3|0ˆk�r3|0ˆ |-d? |/ k�r3|0|%d@< |0|&d@< |!du�rDˆ|"| k�rDt
ˆ|!ƒnˆ‰t‡ ‡fdAd„|& ¡ D ƒƒ}4|4�r^|4d \}5}6n
ˆt|dBƒ dC}5}6|-d> �r“|/�r“|2�r“|2|-dD |/  }7t|7dEƒ|3dF< |5|7k�r“|7dG|-dD dH›dI�}5}6|,dk�rž|5ˆ |, nd}8ˆt|	dEƒt|
dEƒt|dEƒt|ƒ|||!|"t|#ƒt|d	ƒt|dEƒdJœ|3¥}9ttt|+dEƒt|5dEƒ|6t|8d"ƒ|9dK�}:||dL k �rðt|:_|:j dM|dN›dO|dL › �¡ ˆ|	k �rt|:_|:j dP|	dQ›�¡ |5ˆ |k�r!t|:_|:j dR|5ˆ dQ›dS|dQ›dT|dN›dU�¡ |:jtk�r)|:S d};t|%ƒ}<dVD ]‹}=tˆƒt|=ƒd	 k �r@�q1ˆ|= ˆ|=d	  }>}?t
dWd„ ˆdtˆƒ|= … D ƒƒ}@|< ¡ D ]Z\}A‰|Ad/k�rk�q`|Ad0k�r™|@ˆˆ  k�r™|dX �o“|�o“t‡ ‡‡fdYd„ˆdtˆƒ|= … D ƒƒ}B|B�s™�q`|?d ˆk�r¹|>d ˆˆ  k�r¹|>dZ ||d[  k�r¹|Aˆ|>f};�q`�q1|d\ }Cd]}D|Cd> �rø|Cd^ }Et ||E d… ƒ}Fˆ�rãt
d_|
ˆ d d` ƒnd_}G|F|Gk}Dt|FdEƒt|GdEƒ|9da< |9db< |;�r0|dc ddk�r0|;\}A‰}>tˆ|d6 d  ˆ|) ƒ}Hˆ|H dk�r0|5ˆ ˆ|H  }8t|HdEƒt|8d"ƒ|:_!|:_"|;�rÉ|;\}A‰}>|D�s[|:j de|A› dfˆdQ›dg|Cd^ › dh|9da di›dj|9db di›dk�¡ nH|8|dl k�r£ˆˆk�r£t#dm|:_|:_$|:j t|>d2 dnƒ�r€|>d2  %do¡ndp› dq|>d dQ›dr|A› dfˆdQ›ds|>dZ dN›dt|>dZ | du›dv�¡ |:S |D�rÉ|:j |8|dl k �rÂdw|A› dfˆdQ›dx|8dQ›dy|dl › �ndw|A› dz�¡ |�r}|du�r}|t
|5| |d{  |d| d ƒ }Iˆd }J|ˆ  k�oð|Ikn  }K|K|9d}< |Cd> �rt&ˆˆ|	|%|||Cƒ\}L}M}N|N|9d~< |L�oˆ|Jd k}Ong }M|Jd |Jd; k�o'ˆ|Jd k}O|K�rY|O�rY|8|dl k�rYt#d|:_|:_$|:j d€|dQ›d�|dQ›d‚|IdQ›�|Cd> �rSdƒndp ¡ |:S |K�re|M�re|:j '|M¡ |:j( d„|dQ›d‚|IdQ›d…�|Cd> �ryd†nd‡ ¡ |!du�ržˆ|"| k�rž|:j( dˆ|!ˆ  dQ›d‰||d[  dN›dŠ�¡ |8|dl k �r¸|:j d‹|8dQ›dy|dl › dŒˆdQ›�¡ |:j(�sÅt|:_|:j d�¡ |:S )ŽzÎstop_floor: minimum stop distance in dollars from volatility.py
    (smart_engine passes it when smart_engine.min_stop is enabled); when
    None, the placeholder max(min_stop_pct, 2x spread) floor applies.ÚscheduleNra   é
   znot enough bars yet)r?   rr   é   r`   )ÚperiodÚaskÚbidru   c                 S   ó   g | ]}|d  ‘qS )r\   rH   rm   rH   rH   rI   rt   Ý   ó    zanalyze.<locals>.<listcomp>iÄÿÿÿc                 s   rk   rl   rH   rm   rH   rH   rI   rp   ß   rq   zanalyze.<locals>.<genexpr>iâÿÿÿr*   c                 S   r¯   )rr   rH   rm   rH   rH   rI   rt   à   r°   r   g      Y@r   r   r   )ÚZoneInfoÚtimezonec                 s   s   � | ]}t |ƒV  qd S ©N)Úint©rn   ÚxrH   rH   rI   rp   è   rq   Úforce_liquidate_timeú:)ÚhourÚminuteÚsecondÚmicrosecondg      N@r   r   r    r   c                    s   g | ]
}|d  ˆ k r|‘qS ©r`   rH   ©rn   Úpr}   rH   rI   rt   ó   s    c                 S   r¯   r½   rH   r¾   rH   rH   rI   rt   õ   r°   éýÿÿÿr   r   r   c                 s   rk   )rc   NrH   rm   rH   rH   rI   rp   ú   rq   c                 S   s,   i | ]\}}|r|d krt  |¡r||“qS )Úsession_high)Ú
volatilityÚis_level_keyrz   rH   rH   rI   Ú
<dictcomp>û   s    ÿÿzanalyze.<locals>.<dictcomp>ry   Úceilingr#   ÚtÚtzinfor$   r   r   r3   Ú	daily_atrÚref_5d_highÚ
ref_5d_lowrs   éd   )Úpos_in_5d_range_pctÚroom_to_5d_high_cÚroom_to_5d_high_atrrÉ   rÊ   r&   r'   Úrange_5d_highc                 3   s(   � | ]\}}|ˆˆ  kr||fV  qd S r³   rH   rz   )ÚbufÚrefrH   rI   rp   $  ó   €& é<   zclear air (typical 60-min move)r(   r   Úrealistic_capzrealistic cap (open + Úgz daily ATR))r[   r   Úatr_1mÚspreadÚshares_per_minÚrising_lowsÚlast_pivot_lowÚceiling_topÚceiling_bottomÚceiling_touchesÚminutes_leftÚ	reachable)r9   r:   r;   r<   r@   r   zthin: z.0fz
 sh/min < zbelow VWAP rC   ztarget z	 away > ~z reachable in z min)r�   ra   c                 s   rk   )rr   NrH   rµ   rH   rH   rI   rp   P  rq   r2   c                 3   s<   � | ]}|d  t ‡ ‡fdd„ˆD ƒƒkr|d ˆk V  qdS )rÆ   c                 3   s(   � | ]}|d  ˆˆ  kr|d V  qdS )rr   rÆ   NrH   )rn   Úy)rÐ   r|   rH   rI   rp   [  rÒ   z$analyze.<locals>.<genexpr>.<genexpr>rr   N)Únextrµ   )rÐ   rZ   r|   rH   rI   rp   Z  s   €  
ÿÿr\   r   r4   Tr+   gš™™™™™©?r   Úbreakout_slope_pctÚflat_band_pctr!   Úlevelzbreakout of ú z
 but last z bars not sloping up (z.3fz% <= z%)r   ÚbreakoutÚstrftimez%H:%Mr7   z bar closed z > z on z sh (z.1fzx avg)zbroke z	 but R:R z < z but price back under itr   r   Úpullback_candidateÚpullback_contextÚpullbackzheld rising low z, turning up in zone ú-z( (reclaim, selling fading, support held)zpullback: buy z holding + zreclaim/fading selling/supportz	green barzbreakout: 1-min close > z on >= z shzR:R z at z1no breakout level or rising-low pullback to trade))rY   r   rƒ   r5   r   r   r   r†   rV   r‡   r„   Úzoneinfor±   Ú
astimezoneÚsplitÚreplaceÚtotal_secondsr£   r�   r
   r‰   rS   ÚpopÚhasattrrD   rˆ   Úsortedr   r6   r?   r    Úanyr	   r9   r<   r   r8   rç   r–   Úextendr>   )Pr¤   r—   r¥   r¦   r§   rW   r¨   rŠ   Úschedr\   Úa1r×   ÚvolsÚavg_volrØ   rž   r^   r±   ÚtzÚnow_etÚfhÚfmÚfsrÞ   rŸ   Ú	move_spanrß   ÚpivotsÚbelowÚlast_lowÚlast3rÙ   ÚwindowÚc_topÚc_bottomÚclusterÚhodr]   ÚtargetsÚfirst_touchÚageÚfloorÚ
candidatesr9   ÚriskÚf5Úlv_inÚdatrÚh5Úl5Úday_openÚfiveÚabover:   r;   ÚcapÚrrr@   Úplanræ   Úlvlsre   ro   ÚprevÚclosed_beforeÚnameÚ	reclaimedr_   Úslope_okÚnÚslope_nÚflatÚb_stopÚzone_topÚlast_barÚin_zoneÚokr�   ÚdetailsÚturningrH   )rÐ   rZ   r|   r[   rÑ   rI   ÚanalyzeÎ   s–  
,(
ÿÿ
 4


 (



ý0&
ûú
ÿ

ÿ"
(ÿ6
€ò
 

 ÿÿ*ÿÿÿÿ
þ$ÿ
ÿ
ÿ
ÿÿþÿ
ÿ&r*  )NNN) Ú__doc__Údataclassesr   r   r   Ú
statisticsr   Úconfig_loaderr   Ú
indicatorsr   r   r	   Úexit_resistance_stallr
   rÂ   r   r   r   rU   r5   rY   rR   rQ   rS   Útupler–   r´   r�   r£   rO   r*  rH   rH   rH   rI   Ú<module>   sœ    +ÿþýüûúùø	÷
öõôóòìá à!
øÓ:ÿ
ÿ+
ÿÿÿÿÿ