a
    Â‰j®2  ã                   @   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	 e	dƒZ
dZdd„ Zd	d
„ Zdd„ Zdd„ ZdddddœZdd„ Zdd„ Zdd„ Zdd„ Zedkr¤eƒ  dS )a&  Is our Pinnacle feed too stale to anchor the strategy?

Reads research/pin_fresh_capture.csv (real-time Pinnacle from pinnapi) against the
API-Football relay we have used for everything to date, and answers four
questions in increasing order of importance:

  1. HOW STALE? update frequency of each feed.
  2. HOW WRONG? distribution of |fresh_fair - stale_fair| in cents. This is the
     size of the error sitting inside every CLV number we have ever computed.
  3. DOES THE SIGNAL SURVIVE? recompute the xlow band on BOTH feeds and compare
     which fixtures/sides qualify. Three outcomes matter:
       - signals that fire on both        -> genuine, staleness was not driving them
       - fire on stale only               -> ARTEFACTS. we were betting lag.
       - fire on fresh only               -> edge we have been blind to
  4. WHICH ANCHOR DOES KALSHI TRACK? for each side, does the Kalshi mid move
     toward fresh fair or stale fair over the following hour? If Kalshi tracks
     fresh Pinnacle, fresh is the true anchor and our band is mis-calibrated.

Question 3 is the one that decides whether to pay for a feed. A large
"fire on stale only" bucket means the historical edge was partly an artefact of
measuring a live market against a 6-hour-old line.

Usage:  python research\pin_fresh_vs_stale.py
é    N)Údefaultdict)ÚPathz(E:\soccer\research\pin_fresh_capture.csv)gš™™™™™©?g¸…ëQ¸¾?c              	   C   s(   z
t | ƒW S  ttfy"   Y d S 0 d S ©N)ÚfloatÚ	TypeErrorÚ
ValueError©Úx© r
   ú(E:\soccer\research\pin_fresh_vs_stale.pyÚ_f$   s    
r   c              	   C   sP   zt j t| ƒ dd¡¡}W n ttfy4   Y d S 0 |jr@|S |jt jj	d�S )NÚZz+00:00)Útzinfo)
ÚdtÚdatetimeZfromisoformatÚstrÚreplacer   r   r   ÚtimezoneZutc)ÚsÚdr
   r
   r   Ú_pt+   s
    r   c                    s>   t dd„ | D ƒƒsdS dd„ | D ƒ}t|ƒ‰ ‡ fdd„|D ƒS )z9Proportional devig -> implied probabilities summing to 1.c                 s   s   | ]}|o|d kV  qdS ©é   Nr
   ©Ú.0r	   r
   r
   r   Ú	<genexpr>5   ó    zfair.<locals>.<genexpr>Nc                 S   s   g | ]}d | ‘qS )ç      ð?r
   r   r
   r
   r   Ú
<listcomp>7   r   zfair.<locals>.<listcomp>c                    s   g | ]}|ˆ  ‘qS r
   r
   r   ©r   r
   r   r   9   r   )ÚallÚsum)ÚoÚrr
   r   r   Úfair3   s
    r$   c                  C   sd  t  ¡ stdt › d�ƒ‚g } tt dd���$}t |¡D �]}tt|d ƒt|d ƒt|d ƒgƒ}tt|d ƒt|d	 ƒt|d
 ƒgƒ}t|d ƒ}|r6|d u r¢q6|  	||d |d |d f|d ||t|d ƒ| 
d¡| 
d¡| 
dd¡| 
dd¡| 
dd¡| 
d¡t| 
d¡ƒt| 
d¡ƒt| 
d¡ƒt| 
d¡ƒdœ¡ q6W d   ƒ n1 �sV0    Y  | S )Nzno capture yet at z' -- run main\pin_fresh_capture.py firstzutf-8)ÚencodingZfresh_hZfresh_dZfresh_aZstale_hZstale_dZstale_aZtsÚlcÚhomeÚawayZkickoff_utcÚverdictÚ Ú
xlow_freshÚ
xlow_staleZ	game_dateÚ
k_home_midÚ
k_away_midÚ
p_home_midÚ
p_away_mid)ÚtÚkeyr&   ÚfreshÚstaleZkor'   r(   r)   r+   r,   Úgdr-   r.   r/   r0   )ÚSRCÚexistsÚ
SystemExitÚopenÚcsvZ
DictReaderr$   r   r   ÚappendÚget)ÚrowsZfhr#   ÚfrÚstr1   r
   r
   r   Úload<   s0    &&




ð*r@   r-   r.   r/   r0   ))r'   Úkalshi)r(   rA   )r'   Úpoly)r(   rB   c                 C   s|   | D ]r}|  |¡pd ¡ }|rd|vr(q| dd¡\}}|  t  ||fd¡¡}|rd|  k rfdk rn q||f  S qdS )z~(side, price) of the FIRST snapshot where `col` fires, mirroring
    fire_xlow's one-bet-per-fixture, no-look-ahead selection.r*   ú/r   r   N)r<   ÚstripÚsplitÚ	PRICE_COL)ÚsnapsÚcolr#   ÚvÚsideZbookÚpr
   r
   r   Ú
_first_bet_   s    rL   c                 C   s@   | sdS | \}}|dkr |dkn|dk}|r8d| | nddfS )zI(pnl, staked) in units for one bet, or (0, 0) if the anchor did not fire.)ç        rM   r'   ÚHÚAr   g      ð¿r   r
   )ZbetÚftrrJ   rK   Zwonr
   r
   r   Ú_retm   s
    rQ   c           #      C   s²  zddl }W n ty(   tdƒ Y dS 0 i }|jdd�D ]D}|d rR|d d ni }|d ||d |d	 | d
¡| d¡f< q:ttƒ}| D ]*}||d |d |d
 |d f  |¡ qŒ| ¡ D ]}|j	dd„ d� qÀg ddgddgdœ }}	tdd„ ƒ}
d}| 
¡ D �]v\}}| |¡}|dv�r(�q|d7 }t|dƒt|dƒ }}t||ƒ\}}t||ƒ\}}|	d d  |7  < |	d d  |7  < |	d d  |7  < |	d d  |7  < | || ¡ |�r|�r|
d d  |7  < |
d d  d7  < |
d d  |7  < |
d d  d7  < n^|�rN|
d d  |7  < |
d d  d7  < n.|�r|
d d  |7  < |
d d  d7  < �qtdƒ |�sštdƒ dS td|› d �ƒ dD ]f}|	| \}}|dk�rÌd!nd"}t|�rd|› d#|d$›d%|d&›d'|| d( d)›d*�	n
d|› d+�ƒ �q®t|ƒ}t |¡}|dk�r<t |¡nd}|�rT|t |¡ nd}|�rf|| nd}|d,|  |d,|   }}td-ƒ td.|› d/|d0›d1|d2›d3|d4›�ƒ td5|d6›d7|d6›d8�ƒ td9td:d;„ |D ƒƒ› d<|› d=�ƒ t|ƒd,k�rtd>ƒ nNt|ƒd?k�r2td,| t|ƒ d@ ƒd nd} tdA| �rNdB| dC›dD�ndE ƒ tdFƒ dGD ]J}!|
|! \}}"|"�rbtdH|!dI›dJ|"dK›dL|d)›d'||" d( d&›d*�	ƒ �qbdS )Maª  4. DOES THE ROI DIFFER? -- the question the agreement stats cannot answer.

    Sections 1-3 measure how often the two feeds disagree. Disagreement is not
    a cost: a signal the stale feed invents is only expensive if it LOSES, and
    one it hides is only expensive if it WINS. This prices both.

    Run as PAIRED POLICIES over the same settled fixtures. Comparing two
    independent ROIs throws away the fact that both bet on largely the same
    matches; pairing removes fixture-level variance, which dominates everything
    else at this sample size. A fixture only one anchor fired on contributes
    that arm's P&L and zero to the other -- which is what running one policy and
    not the other actually does to your bankroll.

    PRICED AT MIDS, because mids are what the capture logs. That flatters both
    arms identically, so it cancels in the paired difference; the absolute ROIs
    below are NOT tradeable numbers.
    r   Nz;
4. ROI: forward_test_unique_book not importable -- skippedT)Zrequire_resultZprerP   r5   Zleaguer'   r(   r&   c                 S   s   | d S ©Nr1   r
   r   r
   r
   r   Ú<lambda>–   r   zroi_section.<locals>.<lambda>©r2   rM   )r3   r4   c                   S   s   ddgS )NrM   r   r
   r
   r
   r
   r   rS   ™   r   )rN   ÚDrO   r   r+   r,   r3   r4   úboth (fresh leg)úboth (stale leg)ústale-only [ARTEFACT]úfresh-only [MISSED]z:
4. DOES THE ROI DIFFER? (paired policies, priced at mids)zC   no settled fixtures joined yet -- capture has no played matches.ú   z settled fixtures comparedzfresh (pinnapi)  zstale (API-Ftbl) ú z4.0fz bets  net z+8.2fzu  ROI éd   z+7.2fú%z no betsg\�Âõ(\ÿ?z5
   PAIRED per-fixture difference (fresh minus stale)z     n=z  mean=z+.4fzu  se=z.4fz  t=z+.2fz     95% CI [z+.3fz, z] units per fixturez     differed on c                 s   s   | ]}t |ƒd krdV  qdS )ç•Ö&è.>r   N©Úabs)r   r   r
   r
   r   r   Ä   r   zroi_section.<locals>.<genexpr>z of z3 fixtures -- the rest contribute nothing to the gapz2     SIGNIFICANT: the anchors do not pay the same.r^   é   z9     NOT significant -- no measurable ROI difference yet.z' Resolving an effect this small needs ~ú,z
 fixtures.r*   z
   where they disagree:)rV   rW   rX   rY   z     Z24sz n=z<4dz net )Zforward_test_unique_bookÚImportErrorÚprintZload_fixturesr<   r   Úlistr;   ÚvaluesÚsortÚitemsrL   rQ   ÚlenÚ
statisticsZmeanZstdevÚmathZsqrtr!   r`   Úint)#r=   ÚFÚresZfxZr0Zbyfixr#   rI   ZdiffsZaggZbucketsZn_fixr2   rG   rP   ZbfZbsZpfZkfZpsZksÚnameZnetZstkZlblÚnZmuZsdZser1   ÚloÚhiZneedÚkZcntr
   r
   r   Úroi_sectionv   sš    *(

(((*ÿÿ
ÿ
&$
.ÿþÿrt   c                     sR  t ƒ } ttƒ}| D ]}||d   |¡ q| ¡ D ]}|jdd„ d� q2tt| ƒ› dt|ƒ› d�ƒ dd„ ‰ d	d
„ ‡ fdd„| ¡ D ƒD ƒ}dd
„ ‡ fdd„| ¡ D ƒD ƒ}tdƒ |rötdt 	|¡d›d�t 	|¡rîddt 	|¡ d›d�nd ƒ |�r<tdt 	|¡d›d�t 	|¡�r4ddt 	|¡ d›d�nd ƒ |�sJtdƒ g }| D ]4}|d �sd�qR|dd
„ t
|d |d ƒD ƒ7 }�qRtdƒ |�r"tj|dd�}td t 	|¡d›d!|d" d›d#|d$ d›d%|d& d›d't|ƒd›d(�ƒ td)td*d„ |D ƒƒt|ƒ d d+›d,�ƒ td-ƒ ntd.ƒ td/ƒ ttƒ}ttƒ}	| D ]Œ}| d0¡�pXd ¡ }|�sh�qF||  d17  < | d2¡�p†d| d3¡�p”d }
}|
�s¦|�rF|	|  | d4¡| d5¡| d6¡|
|f¡ �qFt| ¡ ƒ}|�sòtd7ƒ �nTtd8d
„ |	d9 D ƒƒ}td:|› d;�ƒ td<| d9d=¡d>›d?|› �ƒ td@| dAd=¡d>›dB�ƒ tdC| dDd=¡d>›dE�ƒ tdF| dGd=¡d>›�ƒ dHD ]|}|	 |g ¡d dI… D ]`\}}}}
}tdJ|› dK|› dLt|ƒd dM… › dNt|ƒd dM… › dO|
�pèdP› dQ|�pôdP› �ƒ �qž�q†| dAd=¡}|dRk�r6tdSd| | d+›dT�ƒ ntdU|› dV�ƒ t| ƒ d S )WNr2   c                 S   s   | d S rR   r
   r   r
   r
   r   rS   Ý   r   zmain.<locals>.<lambda>rT   z snapshots across z
 fixtures
c                    sv   ‡ fdd„| D ƒ}t |ƒdk r"d S | d d | d d   ¡ d }|dkrNd S tdd	„ t||d
d … ƒD ƒƒ}|| S )Nc                    s,   g | ]$}|  ˆ ¡rtd d„ |ˆ  D ƒƒ‘qS )c                 s   s   | ]}t |d ƒV  qdS )é   N)Úroundr   r
   r
   r   r   â   r   z0main.<locals>.rate.<locals>.<listcomp>.<genexpr>)r<   Útuple)r   r#   ©rs   r
   r   r   â   r   z&main.<locals>.rate.<locals>.<listcomp>é   éÿÿÿÿr1   r   i  c                 s   s   | ]\}}||krd V  qdS r   r
   ©r   ÚaÚbr
   r
   r   r   è   r   z%main.<locals>.rate.<locals>.<genexpr>r   )ri   Ztotal_secondsr!   Úzip)rI   rs   ZvalsÚspanZchr
   rx   r   Úrateá   s      zmain.<locals>.ratec                 S   s   g | ]}|d ur|‘qS r   r
   r   r
   r
   r   r   ê   r   zmain.<locals>.<listcomp>c                 3   s   | ]}ˆ |d ƒV  qdS )r3   Nr
   ©r   rI   ©r€   r
   r   r   ê   r   zmain.<locals>.<genexpr>c                 S   s   g | ]}|d ur|‘qS r   r
   r   r
   r
   r   r   ë   r   c                 3   s   | ]}ˆ |d ƒV  qdS )r4   Nr
   r�   r‚   r
   r   r   ë   r   z>1. UPDATE FREQUENCY (changes per hour, median across fixtures)z   fresh Pinnacle (pinnapi)  z.2fz/hz
  ~ every é<   z.0fz minr*   z   stale Pinnacle (API-Ftbl) z<   (no stale odds captured -- API-Football returned nothing)r4   c                 S   s    g | ]\}}t || ƒd  ‘qS )r\   r_   r{   r
   r
   r   r   û   r   r3   zE
2. DISAGREEMENT between the two feeds (cents of implied probability)r\   )rp   z
   median zc   p75 éJ   zc   p90 éY   zc   p99 éb   zc   max Úcz%   share of legs disagreeing by >2c: c                 s   s   | ]}|d krdV  qdS )ra   r   Nr
   r   r
   r
   r   r     r   z.1fr]   zY   For scale: the xlow band is 5-12% of fair, and a 25c leg in band is 1.25-3c from fair.z   no paired rows yetz 
3. WOULD THE SAME SIGNALS FIRE?r)   r   r+   r,   r&   r'   r(   z?   no scored snapshots yet -- capture has not written verdicts.c                 S   s    g | ]}|d  r|d rd‘qS )ry   ru   r   r
   r�   r
   r
   r   r     r   ZsamerZ   z scored snapshotsz   agree (same verdict)     : r   Z5dz   of which BOTH fired: z   stale-only  [ARTEFACT]   : Ú
stale_onlyz7   <- signals we bet that a fresh line does NOT supportz   fresh-only  [MISSED]     : Ú
fresh_onlyz'   <- edge the stale feed hides from usz   differ (side/book)       : Údiffer)rˆ   r‰   rŠ   é   z      [z] r[   é   z v z  fresh=ú-z stale=éÈ   z
   VERDICT: z/% of scored snapshots are stale-only artefacts.z
   Sample still small (z" snapshots); treat as directional.)r@   r   re   r;   rf   rg   rd   ri   rj   Zmedianr~   Z	quantilesÚmaxr!   rl   r<   rD   r   rt   )r=   Zbyfxr#   rI   r>   Úsrr   ÚqZverdZfiredZxfZxsÚtotalZbothZbucketr&   Úhr|   Zartr
   r‚   r   Úmain×   s’    	  $ÿ&ÿ
$ÿÿÿ
,
".ÿ"4ÿÿ
r”   Ú__main__)Ú__doc__r:   r   r   rk   rj   Úcollectionsr   Úpathlibr   r6   ZBANDr   r   r$   r@   rF   rL   rQ   rt   r”   Ú__name__r
   r
   r
   r   Ú<module>   s*   	ÿ	a[