o
    ï|¼j0  ã                   @   sŒ   d Z ddlmZ ddlmZ edƒZddddd	d
dddddœ
ZddddddddœZdefdd„Z	dd„ Z
dd„ Zd$d d!„ZG d"d#„ d#ƒZdS )%a3  
playbook.py -- [2026-09-29] Situation playbook (the user's plan: group each stock by its
behavior pattern, find where winners and losers of each group differ, and tell the bot
what to do in each situation). Groups and rules from reports/open_window/group_study.py
(23 days, 676 stock-days, IN-SAMPLE -- see that script's output before trusting them).

Every stock gets ONE group from the shape of its first 45 minutes (decided on the 10:15
bar, i.e. from ~10:16 ET), then a verdict for the day, fixed at the minute the study
tested it:
  1 OPENING DRIVE    (>= +2% over the open, above VWAP, within 1% of the high at 10:15)
                     ALLOW if at 10:20 it's still within near_high_pct of the day high,
                     else AVOID (the drive is over)
  2 UP, PULLED BACK  (>= +1% over the open, above VWAP, > 1% off the high)
                     ALLOW if at 10:15 steady (R2 > g2_min_r2) AND normal volume
                     (RVOL < g2_max_rvol), else AVOID
  3 V RECOVERY       (fell >= 2% under the open, back above VWAP)
                     ALLOW if at 10:15 RVOL < g3_max_rvol AND last-15-min volume <
                     g3_max_vol15x its normal, else AVOID (heavy-volume V = fade)
  4 SPIKE & FADE     (first-30-min high >= +2%, now below VWAP)      AVOID all day
  5 SELLING SLIDE    (<= -2% under the open, below VWAP)             AVOID all day
  6 WEAK DRIFT       (below VWAP, the rest)                          AVOID all day
  7 FLAT / CHOP      (above VWAP, the rest)                          AVOID all day
Before the verdict minute: WAIT (before_group: "wait"; "allow" lets v2 buy as before).
ALLOW only means v2 may buy -- v2's own rules still decide when.

classify() is pure (bars + 14-day reference in, verdict out); monitor.py and
simulate.py call the same function and cache the day's verdict per symbol.
é    )ÚZoneInfo)Ú_r2_effzAmerica/New_YorkFÚcontrolÚwaité-   é2   g…ëQ¸å?gìQ¸…ëÑ?g®Gáz®÷?g®Gáz®@g®Gáz@)
ÚenabledÚmodeÚbefore_groupÚgroup_minuteÚ	g1_minuteÚnear_high_pctÚ	g2_min_r2Úg2_max_rvolÚg3_max_rvolÚg3_max_vol15xzOPENING DRIVEzUP, PULLED BACKz
V RECOVERYzSPIKE & FADEzSELLING SLIDEz
WEAK DRIFTzFLAT / CHOP)é   é   é   é   é   é   é   Úreturnc                 C   s"   |   t¡}|jd d |j d S )Né	   é<   é   )Ú
astimezoneÚETÚhourÚminute)ÚtsÚt© r#   ú#/var/www/screener/trade/playbook.pyÚ_idx4   s   
r%   c              	      sè  ‡ fdd„| D ƒ}|sdS |d d }g |d d d}}}dd„ |D ƒ}t ˆ d ƒD ]}	|	|v r8||	 d	 }| |¡ q,|ˆ  }
td
d„ |D ƒƒ}tdd„ |D ƒƒ}tdd„ |D ƒƒ}tdd„ |D ƒƒ}|rl|| n|
}dd„ |D ƒ}ˆ |
||
| d d |
| d d |
| d d || d d |r t|ƒ| d d nddœ}t|ˆ d ˆ d … ƒ\}}|j||d� |rë|t|d ˆ  dƒ |d< t|d ˆ d ˆ d … ƒ}t‡ fdd„|D ƒƒ}|rç|| nd|d< dd„ | ¡ D ƒS )zMMeasures on the bar of minute m (forward-filled), bars = finished 1-min bars.c                    s0   g | ]}d t |d ƒ  krˆ krn n|‘qS )r   r"   ©r%   ©Ú.0Úb©Úmr#   r$   Ú
<listcomp>;   s   0 z_state_at.<locals>.<listcomp>Nr   Úoc                 S   s   i | ]	}t |d  ƒ|“qS )r"   r&   r'   r#   r#   r$   Ú
<dictcomp>@   s    z_state_at.<locals>.<dictcomp>r   Úcc                 s   ó   � | ]}|d  V  qdS )ÚhNr#   r'   r#   r#   r$   Ú	<genexpr>F   ó   € z_state_at.<locals>.<genexpr>c                 s   r0   )ÚlNr#   r'   r#   r#   r$   r2   G   r3   c                 s   r0   )ÚvNr#   r'   r#   r#   r$   r2   H   r3   c                 s   s4   � | ]}|d  |d  |d  d |d  V  qdS )r1   r4   r/   r   r5   Nr#   r'   r#   r#   r$   r2   I   s   €2 c                 S   s$   g | ]}t |d  ƒdk r|d ‘qS )r"   r   r1   r&   r'   r#   r#   r$   r,   K   s   $ éd   g        )r    ÚpriceÚopenÚret_openÚvwap_pctÚoff_highÚlow_pctÚfirst30_hi_pcté   )Úr2ÚeffÚcum_volÚrvolÚvol_per_minc                 3   s<   � | ]}ˆ d  t |d ƒ  krˆ krn n|d V  qdS )r>   r"   r5   Nr&   r'   r*   r#   r$   r2   U   s   €: Úvol15xc                 S   s*   i | ]\}}|t |tƒrt|d ƒn|“qS )r   )Ú
isinstanceÚfloatÚround)r(   Úkr5   r#   r#   r$   r.   W   s   * )ÚrangeÚappendÚmaxÚminÚsumr   ÚupdateÚitems)ÚbarsÚrefr+   Úuptor-   ÚclosesÚlastÚjÚbyrH   r/   ÚhiÚloÚvolÚpvÚvwapÚfirst30Úsr?   r@   Únorm15Úv15r#   r*   r$   Ú	_state_at9   s<   ýr`   c                 C   s¬   | d dkr| d dkr| d dkrdS | d dkr"| d dkr"dS | d d	kr0| d dkr0d
S | d dkr>| d dk r>dS | d d	krL| d dk rLdS | d dk rTdS dS )Nr9   r   r:   r   r;   éÿÿÿÿr   r<   éþÿÿÿr   r=   r   r   r   r   r#   )r]   r#   r#   r$   Ú_groupZ   s   $rc   Nc              	   C   sØ  i t ¥|pi ¥}| r| dd… ng }|rt|d d ƒnd}|d |d }}||k rAdddddt|d	 d
ƒ› d|d	 › d�dœS |sKddddddœS t|||ƒ}t|ƒ}	|	t|	 |ddœ}
|	d	kr£||k rp|
jdddd� |
S t|||ƒ}||
d< |d |d  kr“|
jdd|d d›d�d� |
S |
jdd|d d›d�d� |
S |	dk�r| d¡}|d  |d! krÓ|durÓ||d" k rÓ|
jdd#|d  d$›d%|d$›d&�d� |
S g }|d  |d! kré| d'|d  d$›d&�¡ |du sô||d" k�r| |du�rd(|d$›d&�nd)¡ |
jdd*d+ 	|¡ d� |
S |	d,k�r]| d¡| d-¡}}|du�rM||d. k �rM|du�rM||d/ k �rM|
jdd0|d$›d1|d$›d2�d� |
S |
jdd3|› d1|› d2�d� |
S |
jdd4t|	 › d5�d� |
S )6a  bars: today's 1-min bars ({"t","o","h","l","c","v"}), the forming one may be last.
    Returns {"action": ALLOW|AVOID|WAIT, "final": bool, "group", "group_name", "reason",
    "at_1015", "at_1020"}; final=True means the verdict holds for the rest of the day.Nra   r"   r   r   ÚWAITFz*playbook: group decided on the 10:15 bar (r   r   z of z bars))ÚactionÚfinalÚgroupÚ
group_nameÚreasonÚAVOIDTz"playbook: no 14-day reference file)rg   rh   Úat_1015rf   z=playbook: OPENING DRIVE -- checking the high on the 10:20 bar)re   rf   ri   Úat_1020r;   r   ÚALLOWz4playbook: OPENING DRIVE still at its high at 10:20 (z+.2fz%))re   ri   z playbook: OPENING DRIVE over -- z% off the high at 10:20r   rB   r?   r   r   z&playbook: UP, PULLED BACK, steady (R2 z.2fz) on normal volume (RVOL ú)znot steady (R2 zheavy volume (RVOL zno RVOLzplaybook: UP, PULLED BACK -- z, r   rD   r   r   z,playbook: V RECOVERY on normal volume (RVOL z	, 15-min zx)z+playbook: V RECOVERY on heavy volume (RVOL z
playbook: z -- avoid all day)
ÚDEFAULTSr%   rK   r`   rc   ÚGROUPSrN   ÚgetrJ   Újoin)rP   rQ   Úcfgr/   ÚdonerT   ÚGÚG1r]   ÚgÚoutÚs2ÚrvÚwhyÚvxr#   r#   r$   Úclassifyj   s^   ÿÿì
í
$$ò"	
ø0 ýÿr}   c                   @   s0   e Zd ZdZd
dd„Zdd„ Zdefdd	„ZdS )ÚPlaybookzEPer-day cache of final verdicts (a verdict never changes once final).Nc                 C   s   i t ¥|pi ¥| _i | _d S ©N)ro   rs   Ú_final)Úselfrs   r#   r#   r$   Ú__init__ž   s   
zPlaybook.__init__c                 C   s:   | j  |¡}|d u rt||| jƒ}| d¡r|| j |< |S )Nrf   )r€   rq   r}   rs   )r�   ÚsymbolrP   rQ   r5   r#   r#   r$   Úcheck¢   s   

zPlaybook.checkr   c                 C   sZ   | j  d¡r| j  d¡dkrdS |d dkr'| d¡du r'| j  d	¡d
kr'dS |d dkS )z>True when this verdict must stop a v2 buy (control mode only).r   r	   r   Fre   rd   rg   Nr
   Úallowrm   )rs   rq   )r�   Úverdictr#   r#   r$   Úblocksª   s
   *zPlaybook.blocksr   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r‚   r„   Úboolr‡   r#   r#   r#   r$   r~   ›   s
    
r~   r   )r‹   Úzoneinfor   Úentry_v2r   r   ro   rp   Úintr%   r`   rc   r}   r~   r#   r#   r#   r$   Ú<module>   s.    öÿ!
1