a
    •ÇAj.  ã                   @   s´   d Z ddlZddlZddlZddlZddlmZ ddlmZ dZdZ	dZ
g d¢Zd	d
„ Zdd„ Zdd„ Zdd„ Zdd„ Zdd„ Zdd„ Zddd„Zdd„ Zdd„ Zedkr°eƒ  dS ) aå  
OOS test of the "unique-bet" hypothesis: bets where exactly ONE book (Kalshi XOR
Poly) fires a [0.05,0.12) signal backtested BETTER than bets where BOTH fire
(Kalshi-only +63.9% z+2.10, Poly-only +59.6% z+2.89 vs both ~+8-11%). That was
in-sample, hand-sliced, small n. Here we hold it out.

The rule IS implementable at bet time: pre-KO you can see whether only one book is
in-band (no look-ahead). So "bet only the one-book-only signals" is a real strategy.

Protocol: restrict to SHARED-PRICE fixtures (both books quoted, fair test), order by
game_date, 60/40 temporal split. Report TRAIN vs TEST for:
  - UNIQUE  (Kalshi-only @ ask  +  Poly-only @ mid)   <- the hypothesis
  - OVERLAP (both fired; Kalshi side @ ask)            <- the baseline
If the unique edge is real it should persist (stay +, ideally > overlap) in TEST.
Caveat baked in: Poly-only priced at MID (no historical ask) = optimistic; also shown
Poly-only @ est-ask (near-term half-spread) for honesty. Multiplicative de-vig.
é    N)Údatetime)ÚdefaultdictzE:\soccer\poly_backfill)çš™™™™™©?g¸…ëQ¸¾?g333333ã?)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\research\kalshi_poly_unique_oos.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   c                    s(   dd„ | D ƒ}t |ƒ‰ ‡ fdd„|D ƒS )Nc                 S   s   g | ]}d | ‘qS )g      ð?r   )Ú.0r   r   r   r   Ú
<listcomp>(   ó    zfair_mult.<locals>.<listcomp>c                    s   g | ]}|ˆ  ‘qS r   r   )r   Zri©r
   r   r   r    (   r!   )Úsum)Úor   r   r"   r   Ú	fair_mult'   s    r%   c                 C   s(   | dk rdS | dk r$| dk r dS dS dS )Nr   gú~j¼t“h?gffffffÖ?g      Ð?g{®Gázt?gš™™™™™™?g{®Gáz”?r   )Úmidr   r   r   Úhalf_spread)   s    r'   c               
   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 ]l\}}|\}}}	}
|s´qštd	d
„ |D ƒdd„ d�}|sÔqšt|ƒ}|d u ræqš| t|||dd
„ |D ƒd�¡ qš|jdd„ d� |S )Nz*_polybackfill.csvr   zutf-8)ÚnewlineÚencodingZ	game_dateÚleagueÚhomeÚawayc                 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   ©r   r   r   r   r   r    9   s   "ÿzload.<locals>.<listcomp>c                 S   s   | d S ©Nr   r   ©Útr   r   r   Ú<lambda>;   r!   zload.<locals>.<lambda>©Úkeyc                 S   s   g | ]\}}|‘qS r   r   )r   Ú_r   r   r   r   r    ?   r!   )Úgdr*   r   Úprec                 S   s   | d S ©Nr5   r   )r   r   r   r   r1   @   r!   )r   ÚlistÚglobÚosÚpathÚjoinÚDIRÚopenÚcsvZ
DictReaderr   ÚappendÚitemsÚsortedr   ÚdictÚsort)Zfixturesr;   Zfhr   Úfxr3   r   r5   r*   r+   r,   r6   r   r   r   r   Úload.   s,    ÿÿ&þ
"rF   c              	   C   sî  g }d}dD �] \}}| d D �]Œ}t | d¡ƒt | d¡ƒt | d¡ƒ  }}}	d|||	fv s|dkr|dkr|	dkszq|d	kr°t | d
|› d�¡ƒ}
t | d
|› d�¡ƒ}nt | d|› d�¡ƒ}
|
}|
du s|du sd|
  k rîdk rn qd|  k �r
dk �sq qd}t|||	fƒ| }|�r6||
 | nd}|du std |  k�rbtd k �sfq q| d dk�r~|dk�p’| d dk�o’|dk}| ||||
|f¡  qqq|�s¾d|fS t|dd„ d�\}}}}
}t|||
||d�|fS )zUbook='kalshi' (settle @ ask) or 'poly' (settle @ mid). Returns bet dict + have_price.F))r+   r   )r,   é   r6   Zpin_homeZpin_drawZpin_awayNé   ÚkalshiZkalshi_Z_midZ_askZpoly_Z_pricer   Tr   r   r+   r   r,   c                 S   s   | d S r.   r   r/   r   r   r   r1   V   r!   zfire.<locals>.<lambda>r2   )ÚsideÚpxr&   ÚwonÚclv)r   r   r%   ÚBANDr@   ÚmaxrC   )rE   ZbookZcandsZhaverJ   Zsidxr   ÚphZpd_Zpar&   rK   ZpfrM   rL   r   r   r   ÚfireC   s$    .(.D,.rQ   rK   c                 C   sÜ   t | ƒ}|sd S tdd„ | D ƒƒ}d } }}| D ]P}||v rF|| n|d }||d rdd| | nd7 }||7 }||d|  7 }q2|dkrž|| t |¡ nd}	d	t |	t d
¡ ¡ }
t||| d || d |	|
d�S )Nc                 s   s   | ]}|d  V  qdS )rL   Nr   ©r   Úbr   r   r   Ú	<genexpr>\   r!   zstat.<locals>.<genexpr>g        rK   rL   rH   g      ð¿r   g      à?rG   éd   )ÚnÚwrÚroiÚzÚp)Úlenr#   ÚmathZsqrtZerfcrC   )ZbetsÚpricerV   ZwinsZnetZmuÚvarrS   rZ   rY   Zpvalr   r   r   ÚstatY   s    r_   c                 C   sj   |st d| d›d�ƒ d S t d| d›d|d d›d|d d	›d
|d d›d|d d›d|d d›�ƒ d S )Nz    Z32sz
 (no bets)z n=rV   Z3dz  WR=rW   z4.1fz%  ROIturn=rX   z+6.1fz%  z=rY   z+.2fz  p=rZ   z.3f)Úprint)Útagr
   r   r   r   Úlinef   s    0ÿÿrb   c                  C   s®  t ƒ } g }| D ]B}t|dƒ\}}t|dƒ\}}|r|s8q| t|d ||d�¡ q|jdd„ d� tt|ƒt ƒ}|d |… ||d …  }}	tdt|ƒ› d	td
›dt|ƒ› d|d d › dt|	ƒ› d|	d d › d�ƒ dd„ }
d|fd|	ffD ]€\}}|
|ƒ\}}}}|| }td|› d�ƒ t	dt
|ƒƒ t	dt
|ƒƒ t	dt
|ƒƒ t	dt
|dd�ƒ t	dt
|ƒƒ tƒ  qð|
|ƒ\}}}}tdƒ t	dt
|| ƒƒ t	dt
|ƒƒ d S ) NrI   Zpolyr5   )r5   ÚkrZ   c                 S   s   | d S r7   r   )r   r   r   r   r1   s   r!   zmain.<locals>.<lambda>r2   zshared-price fixtures=z
  split @ z.0%z: train=z (<= éÿÿÿÿz)  test=z (>= r   z)
c                 S   sD   dd„ | D ƒ}dd„ | D ƒ}dd„ | D ƒ}dd„ |D ƒ}||||fS )Nc                 S   s$   g | ]}|d  r|d s|d  ‘qS ©rc   rZ   r   r-   r   r   r   r    z   r!   z)main.<locals>.buckets.<locals>.<listcomp>c                 S   s$   g | ]}|d  r|d s|d  ‘qS )rZ   rc   r   r-   r   r   r   r    {   r!   c                 S   s$   g | ]}|d  r|d r|d  ‘qS re   r   r-   r   r   r   r    |   r!   c                 S   s(   g | ] }t ||d  t|d  ƒ d�‘qS )r&   )Úpxa)rC   r'   rR   r   r   r   r    ~   r!   r   )ÚrecsÚkonlyÚponlyÚboth_kÚ	ponly_askr   r   r   Úbucketsy   s
    zmain.<locals>.bucketszTRAIN (in-sample)zTEST (held-out OOS)z== z ==zUNIQUE pooled (K@ask + P@mid)z  Kalshi-only @ askz  Poly-only   @ midz  Poly-only   @ est-askrf   )r]   zOVERLAP both-fired (K@ask)z8== FULL sample (hypothesis-generating; for reference) ==)rF   rQ   r@   rC   rD   Úintr[   ÚSPLITr`   rb   r_   )rE   rg   r   rc   ZkhrZ   rP   ZcutZtrZterl   ZlabelÚpartrh   ri   rk   rj   Zunique_pooledr   r   r   Úmaink   sB    
ÿ
ÿÿ
ÿ
rp   Ú__main__)rK   )Ú__doc__r?   r9   r:   r\   r   Úcollectionsr   r=   rN   rn   r   r   r   r   r%   r'   rF   rQ   r_   rb   rp   Ú__name__r   r   r   r   Ú<module>   s&    
'