a
    kTej‘  ã                   @   sª   d Z ddlZddlZddlZddlmZ ddlmZ ej dd¡ ej dd¡ ddl	Z
ddlZdZddd	d
œZdZdZdd„ Zdd„ Zdd„ Zdd„ Zedkr¦eƒ  dS )a!  
Serious attempt: can an Elo-based model beat the closing line? (read-only)

The first pass ([[project_nonprice_features_dead]]) used only 5/10-game rolling
means and lost to the market. That is a weak representation of team strength --
a fair test needs proper ratings. This builds:

  - ELO from 50,574 fixtures (2017-2025, 18 leagues), computed strictly
    chronologically so a match is always scored with the rating held BEFORE it.
    Home advantage and K are fitted on the training period only.
  - REST DAYS per side (fixture congestion is a real, well-documented effect).
  - GOAL-DIFFERENCE-weighted updates, so a 4-0 moves a rating more than a 1-0.
  - the rolling form features, kept alongside.

Benchmark is unchanged and is the only one that matters: the DEVIGGED PINNACLE
CLOSING price. Beating a naive prior is meaningless; beating the close is the
whole question. Split is TIME-based.

Even a win here is not money -- any edge must then clear the ~0.67c half-spread
(project_xlow_spread_verdict).

Usage:  python research\elo_model_test.py
é    N)Údatetime)ÚdefaultdictzE:\soccer\mainzE:\soccer\researchz$E:\soccer\raw_api\fixtures_index.csvé   é   ©ÚHÚDÚAg      4@g     @P@c              	   C   s,   zt t| ƒƒW S  ttfy&   Y d S 0 d S )N)ÚintÚfloatÚ	TypeErrorÚ
ValueError©Úx© r   ú$E:\soccer\research\elo_model_test.pyÚ_i)   s    r   c                  C   sî   g } t tddd��¸}t |¡D ]œ}t| d¡ƒt| d¡ƒ }}|d u s|d u rRqz t |d d d… d¡ ¡ }W n t	t
fyŒ   Y qY n0 |  t|| d	¡| d
¡| d¡||d�¡ qW d   ƒ n1 sÐ0    Y  | jdd„ d� | S )NÚ zutf-8)ÚnewlineÚencodingZ
home_goalsZ
away_goalsÚdateé
   z%Y-%m-%dÚdivZ	home_teamZ	away_team)r   r   ÚhomeÚawayÚhgÚagc                 S   s   | d S ©Nr   r   r   r   r   r   Ú<lambda>=   ó    zload_fixtures.<locals>.<lambda>©Úkey)ÚopenÚFIXÚcsvZ
DictReaderr   Úgetr   Ústrptimer   r   ÚKeyErrorÚappendÚdictÚsort)ÚoutZfhÚrr   r   Údr   r   r   Úload_fixtures0   s     
ÿ(r.   c                 C   sL  t dd„ ƒ}i }| D �]0}|d |d  }}|| ||  |d< |d< ||v r`|d ||  jnd|d	< ||v r‚|d ||  jnd|d
< ddd|| ||  t d    }|d |d krÂdn|d |d krÖdnd}t|d |d  ƒ}t t|dƒd ¡}	t|	 ||  }
||  |
7  < ||  |
8  < |d  ||< ||< q| S )zHWalk forward; attach the PRE-match rating and rest days to each fixture.c                   S   s   dS )Ng     p—@r   r   r   r   r   r   C   r   zbuild_elo.<locals>.<lambda>r   r   Úelo_hÚelo_ar   é   Úrest_hÚrest_aç      ð?r   ç      y@r   r   g      à?g        r   )r   ZdaysÚHOME_ADVÚabsÚmathÚlogÚmaxÚK_FACTOR)ZfixturesZeloZlastÚfÚhÚaZexp_hZsc_hÚgdZmultZdeltar   r   r   Ú	build_eloA   s     
""$,r@   c                     sh  t tƒ ƒ} tt| ƒ› d�ƒ t ¡ }g }| D ]º}|d t t |d ¡¡t t |d ¡¡f}| 	|¡}|snq,|\}}|d |d  }| 
t|d |||d |d ||t |d |d |d |d  d	d	d
| t d    gd�¡ q,tdt|ƒ› �ƒ t|ƒdk �rtdƒ d S |jdd„ d� tt|ƒd ƒ}	|d |	… ||	d …  }
}tdt|
ƒ› d|
d d › dt|ƒ› d|d d › d�	ƒ dd l}ddlm} ddlm} ddlm} dd„ |
D ƒ‰d d„ |D ƒ‰d!d"„ ‰ˆd#d„ |D ƒˆƒ‰ td$d%›d&d'd(›d)d*d(›�ƒ td+d%›d&ˆ d,›d)d-d(›�ƒ | d.d„ |
D ƒ¡}| d/d„ |D ƒ¡}|ƒ  |¡}| |¡| |¡ }}| d0d„ |
D ƒ¡}| d1d„ |D ƒ¡}‡ ‡‡‡fd2d3„}|d4|||d	d5d6d7�ƒ |d8|||d9d:d;dd<�ƒ d=D ]:}|d>|› d?�| ||g¡| ||g¡||d5d6d7�ƒ �qâ|d@| ||g¡| ||g¡|dAd:dBdd<�ƒ tdCƒ tdDƒ tdEƒ d S )FNz fixtures with Elo + rest daysr   r   r   r/   r0   r2   r3   r4   r   r5   )r   ÚpÚresr   z"matched to Pinnacle closing odds: iè  ztoo few to testc                 S   s   | d S r   r   )r,   r   r   r   r   m   r   zmain.<locals>.<lambda>r    gffffffæ?z  train z (to éÿÿÿÿz	)   test z (r   z on)
)ÚLogisticRegression)ÚHistGradientBoostingClassifier)ÚStandardScalerc                 S   s   g | ]}|d  ‘qS ©rB   r   ©Ú.0r,   r   r   r   Ú
<listcomp>x   r   zmain.<locals>.<listcomp>c                 S   s   g | ]}|d  ‘qS rG   r   rH   r   r   r   rJ   y   r   c                 S   s"   t dd„ t| |ƒD ƒƒ t|ƒ S )Nc                 s   s*   | ]"\}}t  t|t|  d ƒ¡V  qdS )ç•Ö&è.>N)r8   r9   r:   ÚIDX)rI   rA   Úyr   r   r   Ú	<genexpr>|   r   z#main.<locals>.ll.<locals>.<genexpr>)ÚsumÚzipÚlen)ÚPÚYr   r   r   Úll{   s    zmain.<locals>.llc                 S   s   g | ]}|d  ‘qS )rA   r   rH   r   r   r   rJ   ~   r   ÚmodelÚ46sú zlog lossz>10sú  z	vs marketzMARKET (Pinnacle closing)ú10.5fz--c                 S   s   g | ]}|d  ‘qS r   r   rH   r   r   r   rJ   ‚   r   c                 S   s   g | ]}|d  ‘qS r   r   rH   r   r   r   rJ   ƒ   r   c                 S   s   g | ]}d d„ |d D ƒ‘qS )c                 S   s   g | ]}t  t|d ƒ¡‘qS ©rK   ©r8   r9   r:   ©rI   Úvr   r   r   rJ   †   r   ú#main.<locals>.<listcomp>.<listcomp>rA   r   rH   r   r   r   rJ   †   r   c                 S   s   g | ]}d d„ |d D ƒ‘qS )c                 S   s   g | ]}t  t|d ƒ¡‘qS rZ   r[   r\   r   r   r   rJ   ‡   r   r^   rA   r   rH   r   r   r   rJ   ‡   r   c                    s|   |  |ˆ¡ | |¡‰t|jƒ‰ ‡ ‡fdd„ttˆƒƒD ƒ}ˆ|ˆƒ}t| d›d|d›dˆ| d›|ˆk rndnd	› �ƒ |S )
Nc                    s"   g | ]‰ ‡ ‡‡fd d„dD ƒ‘qS )c                    s   g | ]}ˆˆ  ˆ  |¡ ‘qS r   )Úindex)rI   Úk)ÚiÚoÚprr   r   rJ   �   r   z0main.<locals>.run.<locals>.<listcomp>.<listcomp>r   r   )rI   ©rb   rc   )ra   r   rJ   �   r   z%main.<locals>.run.<locals>.<listcomp>rV   rW   rY   rX   z+10.5fz  BEATS MARKETr   )ÚfitZpredict_probaÚlistZclasses_ÚrangerQ   Úprint)Zlabelr	   ÚBrU   rR   r]   ©ÚbaserT   ZyteZytrrd   r   Úrun‰   s    


ÿzmain.<locals>.runzElo ALONE (logistic)i¸  Zmultinomial)ÚCÚmax_iterZmulti_classzElo ALONE (gradient boosting)é   i,  g¸…ëQ¸®?)Z	max_depthrn   Zlearning_rateZrandom_state)g{®Gáz„?gš™™™™™¹?r4   zmarket + Elo (logistic, C=ú)z market + Elo (gradient boosting)é   gš™™™™™©?zF
Elo alone losing to the market is EXPECTED and fine -- the market haszEmore information. The only result that would matter is "market + Elo"zJbeating "market", which would mean Elo knows something the price does not.)r@   r.   rh   rQ   ÚTZ	load_oddsZnormÚDSZ	fdco_namer%   r(   r)   r6   r*   r
   ZnumpyZsklearn.linear_modelrD   Zsklearn.ensemblerE   Zsklearn.preprocessingrF   Zarrayre   Z	transformZhstack)ZfxZoddsZrowsr<   r!   ZhitrA   rB   ZedZcutZtrZteZnprD   rE   rF   ZXtrZXteZscZMtrZMterl   rm   r   rj   r   ÚmainV   s‚    
.
ý&
ÿ

ÿÿÿþÿþrt   Ú__main__)Ú__doc__Úsysr$   r8   r   Úcollectionsr   ÚpathÚinsertZdaily_signalsrs   Znonprice_edge_testrr   r#   rL   r;   r6   r   r.   r@   rt   Ú__name__r   r   r   r   Ú<module>   s&   P