a
    þ5?jp'  ã                   @   sÌ   d Z ddlZddlZddlZddlZddlmZmZ ddlmZ dZ	dZ
dZdZeƒ Zd	\ZZZZg d
¢Zdd„ Zdd„ Zdd„ Zddd„Zdd„ ZdZdd„ Zg d¢Zd dd„Zdd„ ZedkrÈeƒ  dS )!aê  
FORWARD TEST (read-only, trades nothing): executable late-drift momentum filter.

Frozen hypothesis (locked 2026-06-15, see project memory):
  A CLV signal is worth more if the Kalshi mid has ALREADY drifted toward our
  side by the time we bet. The executable, NON-look-ahead rule is:
     1. signal fires when clv_kalshi_<side> first enters [0.05, 0.12)  -> record entry mid
     2. at the LAST pre-KO snapshot (the decision point) check the mid drift
        drift = mid_late - mid_entry
     3. if drift >= 0 (momentum) -> "bet" at the LATE ask, p_win = late pin_fair
        if drift <  0 (dip)      -> skip
  Pricing at the late ask (not the early ask) is what makes it non-look-ahead;
  on these penny-quantized longshots the ask barely moves so it costs ~nothing.

In-sample (<= FREEZE_DATE) this beat baseline flat (+20.2% vs +11.6%) and held a
60/40 temporal split, but the Kelly cut was IS-worse/OOS-better (regime-artifact
signature) and the full-sample gap was only p=0.13. UNPROVEN -> forward test.

This script evaluates the FROZEN rule on fixtures with game_date > FREEZE_DATE
(genuine out-of-sample) and appends one dated row to forward_test_momentum_log.csv
each run, so the forward sample is tracked as it accumulates. NOTHING is traded.
é    N)ÚdatetimeÚdate)ÚdefaultdictzE:\soccer\live_dataz'E:\soccer\forward_test_momentum_log.csvz
2026-06-14)gš™™™™™©?g¸…ëQ¸¾?)ç      Ð?r   ç      ð?g     @�@)z%Y-%m-%d %H:%M:%Sz%m/%d/%Y %H:%Mz%m/%d/%Y %H:%M:%Sz%Y-%m-%d %H:%Mc              
   C   s8   t D ].}zt | |¡W   S  ttfy0   Y q0 qd S ©N)Ú_FMTSr   ÚstrptimeÚ
ValueErrorÚ	TypeError)ÚsÚf© r   ú(E:\soccer\forward_test_drift_momentum.pyÚ_pts#   s    r   c              	   C   s(   z
t | ƒW S  ttfy"   Y d S 0 d S r   )Úfloatr   r
   )Úxr   r   r   Ú_f(   s    r   c                 C   s8   t | ƒD ]*}| d¡pd ¡  ¡ }|dv r|  S qd S )NÚftrÚ )ÚHÚDÚA)ÚreversedÚgetÚupperÚstrip)ÚrowsÚrÚvr   r   r   Ú_result+   s    r    Fc                  C   s˜  t tƒ}t tj td¡¡D ]p}t|ddd��N}t 	|¡D ]2}|| 
d¡| 
d¡| 
d¡| 
d¡f  |¡ q:W d	  ƒ q1 s‚0    Y  qg }| ¡ D �]æ\\}}}}	}
|rš|tv rºqštd
d„ |
D ƒdd„ d�}t|ƒdk râqšt|
ƒ}|d	u rôqšg }dD �]h}d|› �d|› d�d|› d�d|› �f\}}}}d	}|D ]º\}}t| 
|¡ƒ}|d	u �s:td |  k�rztd k �s€n �q:| �rât| 
|¡ƒt| 
|¡ƒ }}|d	u �s:|d	u �s:d|  k �rÐdk �r:n n
||k�sâ�q:|||f} �qö�q:|�sþqüt|d  
|¡ƒ}t|d  
|¡ƒ}t|d  
|¡ƒ}|d	u sü|d	u sü|d	u süd|  k �rddk �shqü qüd	 } }}t|ƒD ]l\}}t| 
|¡ƒt| 
|¡ƒt| 
|¡ƒ  }}}|d	u�r||d	u�r||d	u�r||||  }}} �qê�q||d	u süd|  k �r
dk �sqü qü|d dk�r&|dk�p:|d dk�o:|dk}| t||||d ||||||| d�
¡ qü|rš| t|dd„ d�¡ qš|jdd„ d� |S )at  One bet per fixture (both-sides dedupe -> higher CLV), with entry + late pricing.

    entry_posedge=False (default, FROZEN forward-test rule): enter at the FIRST snapshot
        whose clv_kalshi is in BAND, regardless of whether the ask still clears pin_fair.
    entry_posedge=True (live-mirror): enter at the first in-band snapshot whose ASK is
        already below pin_fair (positive edge at the ask). This matches what live_monitor
        actually does -- it fires the first cycle it sees a qualifying, positive-edge ask --
        and is still NON-look-ahead (acts on the first favorable tick, not the global min).
    z*.csvr   úutf-8©ÚnewlineÚencodingZ	game_dateÚleagueÚhomeÚawayNc                 S   s>   g | ]6}|  d ¡pd ¡ dkrt|d ƒrt|d ƒ|f‘qS )Zstatusr   ZPREZ	timestamp)r   r   r   )Ú.0r   r   r   r   Ú
<listcomp>G   s   "ÿz!build_signals.<locals>.<listcomp>c                 S   s   | d S )Nr   r   )Útr   r   r   Ú<lambda>I   ó    zbuild_signals.<locals>.<lambda>)Úkeyé   )r&   r'   Zclv_kalshi_Zkalshi_Z_midZ_askZ	pin_fair_r   é   r   r   )
Úgdr%   ÚsideÚclvÚa0Úp0ÚaLÚpLÚwonÚdriftc                 S   s   | d S )Nr2   r   )Údr   r   r   r+   m   r,   c                 S   s   | d S )Nr0   r   )r   r   r   r   r+   n   r,   )r   ÚlistÚglobÚosÚpathÚjoinÚLIVE_DIRÚopenÚcsvZ
DictReaderr   ÚappendÚitemsÚEXCLUDE_LEAGUESÚsortedÚlenr    r   ÚBANDr   ÚdictÚmaxÚsort) Zentry_posedgeZfixturesr=   Úfhr   Úsigsr0   r%   r&   r'   r   Zprer   Zcandsr1   ZclvkZmidkZaskkZpfkZentZtsÚcÚavZpvZm0r3   r4   ZmLr5   r6   Zmvr7   r   r   r   Úbuild_signals2   sp    
ÿÿ&þ
ÿ,:62.".ÿ
rO   c                 C   s>   d|  k rdk sn dS | | d|  }t t|dƒt tƒS )Nr   r/   ç        )ÚminrI   Ú
KELLY_MULTÚMAX_BET_PCT)ÚpÚar   r   r   r   Ú_kellyr   s    rV   g      $@c              	   C   s¢  t }d}d}t }d}d}d}	|dv }
| D �]}|dkrD|d dk rDq(|
rP|d n|d }|
rd|d n|d	 }d|  k r€d
k s„q( q(|dkr–||ks–q(|d rªd
| | nd}|dkràt||ƒ}|dkrÊq(tt|| tƒ|ƒ}nt}||| 7 }||7 }|	|| 7 }	|d
7 }|t|d ƒ7 }t||ƒ}t||| | ƒ}q(t||�rP|| d nd|t  d
 d |�r~|t  d|  d
 d nd|d |�r˜|	| d nd|d�S )a_  One of the tracked strategies.
       signal  : 'baseline' -> all bets, EARLY ask (a0/p0)
                 'momentum' -> drift>=0,  LATE ask (aL/pL)
                 'edge'     -> pin_fair>ask, LATE ask (aL/pL); positive-edge SELECTION
                               with NO drift requirement -- isolates the edge gate.
       staking : 'flat'  -> fixed $10 stake every bet
                 'kelly' -> 0.25x fractional Kelly, $1 floor, 25% cap; SKIPS zero-edge bets.
       Returns n, win-rate, total return %, per-bet geometric growth %, max-drawdown %,
       and ROI-on-turnover % (net / dollars staked).r   rP   )ÚmomentumÚedgerW   r8   r5   r3   r6   r4   r/   rX   r7   g      ð¿Úkellyéd   r   )ÚnÚwrÚtotalÚgÚmddÚroiturnÚfinal)ÚBANK0rV   rQ   rI   Ú	MIN_STAKEÚFLAT_DOLLARÚintrH   )ÚbetsÚsignalZstakingZbankr[   ÚwZpeakr_   ZstakedZnetZlateÚbrU   rT   ZunitnetZfracZstaker   r   r   Úevaluatey   s<    


 úrj   ))ZbfÚbaselineÚflatzbaseline flat)Zbkrk   rY   zbaseline kelly)ZmfrW   rl   zmomentum flat)ZmkrW   rY   zmomentum kelly)ZefrX   rl   z	edge flatc                 C   s`  | du rt ƒ } dd„ | D ƒ}t ¡  ¡ tdœ}tD ]º\}}}}|rNt|||ƒnd}|r^|d nd||› d�< |r~t|d d	ƒnd
||› d�< |ržt|d dƒnd
||› d�< |r¾t|d d	ƒnd
||› d�< |rÞt|d d	ƒnd
||› d�< q2tj	 
t¡ }ttdd
dd��>}	tj|	t| ¡ ƒd�}
|�r2|
 ¡  |
 |¡ W d  ƒ n1 �sR0    Y  |S )zÖCompute forward (OOS) metrics for all four combos and append one dated row
    to LOG_CSV. Returns the row dict. Called by main() and by the weekly pipeline
    (weekly_bet_summary.py) so the log grows on schedule.Nc                 S   s   g | ]}|d  t kr|‘qS ©r0   ©ÚFREEZE_DATE©r(   r   r   r   r   r)   ³   r,   z(log_forward_snapshot.<locals>.<listcomp>)Zrun_dateZfreeze_dater[   r   Z_nr]   r.   r   Z_totalr^   é   Z_gr_   Z_mddr`   Z_roiturnrU   r!   r"   )Z
fieldnames)rO   r   ZtodayZ	isoformatro   ÚCOMBOSrj   Úroundr<   r=   ÚexistsÚLOG_CSVr@   rA   Z
DictWriterr:   ÚkeysZwriteheaderZwriterow)rL   ÚforwardÚrowÚtagÚsigÚstkÚnmr9   ÚnewrK   Zwrir   r   r   Úlog_forward_snapshot­   s"       "*r~   c            
      C   s  t ƒ } dd„ | D ƒ}dd„ | D ƒ}tdt› dt|ƒ› dt|ƒ› �ƒ d|fd|ffD ]ž\}}td	|› d
�ƒ |sztdƒ qTtD ]r\}}}}t|||ƒ}	td|d›d|	d d›d|	d d›d|	d d›d|	d d›d|	d d›d|	d d›d�ƒ q~qTt| ƒ td ttƒ› d!t› �ƒ d S )"Nc                 S   s   g | ]}|d  t kr|‘qS rm   rn   rp   r   r   r   r)   Æ   r,   zmain.<locals>.<listcomp>c                 S   s   g | ]}|d  t kr|‘qS rm   rn   rp   r   r   r   r)   Ç   r,   zFREEZE_DATE=z   in-sample n=z   FORWARD n=zIN-SAMPLE (reference)zFORWARD (OOS)z
--- z ---z  (no fixtures yet)z  Z16sz n=r[   Z3dz WR=r\   z4.1fz% total=r]   z+7.1fz% g/bet=r^   z+.4fz% MDD=r_   z+6.1fz
% ROIturn=r`   ú%z
logged forward snapshot (z combos) -> )rO   Úprintro   rF   rr   rj   r~   ru   )
rL   Zinsamplerw   Zlabelrf   ry   rz   r{   r|   r9   r   r   r   ÚmainÄ   s(    "0ÿÿÿr�   Ú__main__)F)N)Ú__doc__rA   r;   r<   Zmathr   r   Úcollectionsr   r?   ru   ro   rG   ÚsetrD   rR   rS   rc   rb   r   r   r   r    rO   rV   rd   rj   rr   r~   r�   Ú__name__r   r   r   r   Ú<module>   s,    
@-
