a
    œí»j› ã                c   @   s$  d Z ddlZddlZddlZddlZddl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dlmZ ddlmZmZmZ ddlZe	je	jfD ]*Zzejddd	� W q˜ eyÀ   Y q˜0 q˜d
d„ ZedƒZe dd¡Ze dd¡Ze dd¡Z dZ!dZ"dZ#dZ$dZ%dZ&dZ'dZ(dZ)dZ*dZ+dZ,dZ-dZ.dZ/dZ0dZ1g d¢Z2g d¢Z3dZ4d Z5d!Z6dZ7d"Z8d#Z9d$Z:d%Z;d&Z<d'e;iZ=d(Z>d)Z?d*Z@e A¡ ZBd+d,„ eBD ƒZCd-d.d/d0d1d1d2d3d4d5d6d6d7d7d8d9d:d;d<d<d=d=d>d>d?d@d@dAdBdCdDdEdEdFdFdGdHdIdJdKdLdLdLdMdNdNdOdPdQdRdSdSdTdUdUdVdVdWdWdXdYdYdZdZd[d\d]d^d_d`dadbdcdddddededfdgdgdhdidjdjdjdkdkdkdldmdndodpdqdrdsdtdudvœbZDdwdx„ ZEdydz„ ZFd{d|„ ZGd}d~„ ZHdd€„ ZId�d‚„ ZJdƒd„„ ZKd…d†„ ZLd‡dˆ„ ZMd‰dŠ„ ZNd‹dŒ„ ZOd�dŽ„ ZPi ZQd�d�„ ZRh d‘£ZSd’d“„ ZTd”d•„ ZUd–d—„ ZVd˜d™„ ZWdšd›„ ZXdœd�„ ZYdždŸd d¡d¢d£œZZd¤d¥„ Z[i Z\d¦d§„ Z]d¨d©„ Z^dªd«„ Z_d¬d­„ Z`dâd¯d°„Zad±d²„ Zbd³d´„ Zcdµd¶„ Zdd·d¸„ Zed¹dº„ Zfdãd»d¼„Zgd½d¾„ Zhd¿dÀ„ ZidÁdÂ„ ZjdÃdÄ„ ZkdÅdÆ„ ZldÇdÈ„ ZmdÉdÊ„ ZndËdÌ„ ZodÍdÎ„ ZpdÏdÐ„ ZqdÑdÒ„ ZrdÓdÔ„ ZsdÕdÖ„ Ztd×dØ„ ZudÙdÚ„ ZvdÛdÜ„ ZwdÝdÞ„ Zxdßdà„ Zyezdák�r eyƒ  dS )äu8  
Daily Away CLV Signal Scanner
Pulls upcoming fixtures + Pinnacle/B365 odds from API-Football.
Runs Tue 9 AM and Fri 12:30 PM via Task Scheduler.
Checks for Away CLV >5% vs Pinnacle fair, emails signals with Kelly sizing,
tracks bankroll.

Strategy: Pure Away CLV â€” bet Away when B365A > Pinnacle fair by >5%.
é    N)ÚPath)ÚMIMEMultipart)ÚMIMEText)ÚdatetimeÚtimezoneÚ	timedeltaúutf-8Úreplace)ÚencodingÚerrorsc                 C   s�   i }ztt | ƒ�X}|D ]B}| ¡ }|r| d¡sd|v r| dd¡\}}| ¡ || ¡ < qW d   ƒ n1 sl0    Y  W n tyŠ   Y n0 |S )Nú#ú=é   )ÚopenÚstripÚ
startswithÚsplitÚFileNotFoundError)ÚpathÚenvÚfÚlineÚkÚv© r   údaily_signals.pyÚload_env-   s    
4r   zE:\soccer\.envZEMAIL_SENDERÚ ÚEMAIL_RECIPIENTÚEMAIL_APP_PASSWORDg      Ð?Fçš™™™™™©?ç¸…ëQ¸¾?zE:\soccer\live_datazE:\soccer\data\bankroll.jsonz#E:\soccer\data\kalshi_bankroll.jsonz!E:\soccer\data\poly_bankroll.jsonz E:\soccer\data\pending_bets.jsonzE:\soccer\data\bet_history.jsonz E:\soccer\data\sent_signals.jsonzE:\soccer\data\signal_log.csvT)ZAR1ZCH1ZD1ZD2ZE0ZE1ZE2ZF1ZI1ZI2ZKO1ÚMLSZN1ZNO1ZNWSLÚP1ZSC0ZSE1ZSP1ÚSP2ZT1)	ZCANZRO1ZBSAZBR2ZCL1ZCO1ZPE1r$   r#   ç      ð?g333333Ã?z"E:\soccer\data\auto_trade_log.jsonz$E:\soccer\logs\auto_trade_errors.logzE:\soccer\data\AUTO_TRADE_KILLZ 81b487be73030f5dec980d2a1ef2c406z!https://v3.football.api-sports.iozx-apisports-keyié  é   é   c                 C   s   i | ]}|t  |¡“qS r   )ÚmapsZleague_name)Ú.0Údr   r   r   Ú
<dictcomp>(  ó    r+   z
Man UnitedzMan CityzNott'm ForestZ	NewcastleZWolvesZLeedszWest HamzSheffield WedsZOxfordz	West BromZ	PeterborozAFC WimbledonzEin FrankfurtzFC KolnZDortmundz
M'gladbachzSt PauliúFortuna Dusseldorfú
Schalke 04ZNurnbergZKaiserslauternúHolstein KielZ	KarlsruheZ
Elversbergz
Ath Bilbaoz
Ath MadridZBetisZCeltaZ	VallecanoZSociedadZEspanolz	La Corunaz
Sociedad BzSp GijonZMilanZVeronaZSudtirolzJuve StabiazParis SGZBrestZRennesZ
Strasbourgz
St EtiennezFor SittardúPSV Eindhovenú
AZ AlkmaarúGo Ahead Eaglesú	NAC BredaZUtrechtZ	GroningenZNijmegenZTwenteúSparta RotterdamZHeraclesz	Sp LisbonzSp BragaúGil VicenteZEstrelaúSanta ClaraZPortoZBenficaZGoztepZ
BuyuksehyrZKocaelisporZ
OlympiakosZArisZAEKZPanetolikosZLevadeiakosú	OFI CreteZHeartsz	St MirrenZDundee)bzManchester UnitedzManchester CityzNottingham ForestzNewcastle UnitedzWolverhampton WanderersZWolverhamptonzLeeds UnitedzWest Ham UnitedzSheffield WednesdayzOxford UnitedzWest Bromwich AlbionzWest BromwichzPeterborough UnitedZPeterboroughZ	WimbledonzEintracht Frankfurtu   FC KÃ¶lnzBorussia Dortmundu   Borussia MÃ¶nchengladbachu   MÃ¶nchengladbachzFC St. Pauliz	St. Pauliu   Fortuna DÃ¼sseldorfr-   r.   u   1. FC NÃ¼rnbergu	   NÃ¼rnbergz1. FC Kaiserslauternr/   zKarlsruher SCzSV ElversbergzAthletic ClubzAthletic Bilbaou   AtlÃ©tico MadridzAtletico Madridz
Real Betisz
Celta VigozRayo VallecanozReal SociedadZEspanyolu   Deportivo de La CoruÃ±azDeportivo de la CorunazDeportivo La CorunazReal Sociedad Bu   Sporting GijÃ³nzSporting GijonzAC MilanzHellas Veronau	   SÃ¼dtirolzSS Juve StabiazParis Saint-GermainZPSGzStade BrestzStade Rennais FCzStade RennaiszRC StrasbourgzStrasbourg Alsaceu   Saint-Ã‰tienneu   AS Saint-Ã‰tiennezFortuna Sittardr0   ZPSVr1   ZAZr2   r3   z
FC UtrechtzFC GroningenzNEC Nijmegenz	FC Twenter4   zHeracles AlmelozSporting CPzSC BragaZBragar5   zVicente BarceloszEstrela Amadorar6   zSanta Clara AzoreszFC Portoz
SL Benficau   GÃ¶ztepeZGoztepezGoztepe Izmiru   Ä°stanbul BaÅŸakÅŸehirzIstanbul BasaksehirZ
BasaksehirZKocaeliZ
OlympiacoszAris Thessalonikiz
AEK AthensZPanaitolikosZ
Levadiakosr7   zHeart of Midlothianz
St. Mirrenz	Dundee FCc                 C   s"   t  | ¡}|| kr|S t | | ¡S )z©Translate API-Football team name to football-data.co.uk equivalent.
    Checks maps.py (team_map.json) first, then the local FDCO_MAP override, then falls back to as-is.)r(   Ú	fdco_nameÚFDCO_MAPÚget)ÚnameZmappedr   r   r   r8   ¢  s    
r8   c                  C   sT   z@t tƒ�$} tt | ¡d ƒW  d   ƒ W S 1 s40    Y  W n   Y dS 0 d S ©NÚbankrollg     @�@)r   ÚBANKROLL_FILEÚfloatÚjsonÚload©r   r   r   r   Úload_bankroll«  s
    
6rC   c                 C   sT   t tdƒ�6}tjt| dƒt ¡  d¡dœ|dd� W d   ƒ n1 sF0    Y  d S ©NÚwé   ú%Y-%m-%d %H:%M)r=   Úupdated©Úindent)r   r>   r@   ÚdumpÚroundr   ÚnowÚstrftime©Zamountr   r   r   r   Úsave_bankroll²  s    rP   c                  C   sT   z@t tƒ�$} tt | ¡d ƒW  d   ƒ W S 1 s40    Y  W n   Y dS 0 d S r<   )r   ÚKALSHI_BANKROLL_FILEr?   r@   rA   rB   r   r   r   Úload_kalshi_bankroll¶  s
    
6rR   c                 C   sT   t tdƒ�6}tjt| dƒt ¡  d¡dœ|dd� W d   ƒ n1 sF0    Y  d S rD   )r   rQ   r@   rK   rL   r   rM   rN   rO   r   r   r   Úsave_kalshi_bankroll½  s    rS   c                  C   sT   z@t tƒ�$} tt | ¡d ƒW  d   ƒ W S 1 s40    Y  W n   Y dS 0 d S r<   )r   ÚPOLY_BANKROLL_FILEr?   r@   rA   rB   r   r   r   Úload_poly_bankrollÁ  s
    
6rU   c                 C   sT   t tdƒ�6}tjt| dƒt ¡  d¡dœ|dd� W d   ƒ n1 sF0    Y  d S rD   )r   rT   r@   rK   rL   r   rM   rN   rO   r   r   r   Úsave_poly_bankrollÈ  s    rV   c                  C   sN   z8t tƒ�} t | ¡W  d   ƒ W S 1 s,0    Y  W n   g  Y S 0 d S ©N)r   ÚPENDING_FILEr@   rA   rB   r   r   r   Úload_pendingÍ  s
    
.rY   c                 C   s>   t tdƒ� }tj| |dd� W d   ƒ n1 s00    Y  d S ©NrE   rF   rI   )r   rX   r@   rK   )Úbetsr   r   r   r   Úsave_pendingÔ  s    r\   c                  C   sN   z8t tƒ�} t | ¡W  d   ƒ W S 1 s,0    Y  W n   g  Y S 0 d S rW   )r   ÚHISTORY_FILEr@   rA   rB   r   r   r   Úload_historyØ  s
    
.r^   c                 C   s>   t tdƒ� }tj| |dd� W d   ƒ n1 s00    Y  d S rZ   )r   r]   r@   rK   )Úhistoryr   r   r   r   Úsave_historyß  s    r`   c                  C   sJ   t  tj¡} | j| j }}|dkr&|n|d }|› t|d ƒdd … › �S )Né   r   rF   )r   rM   r   ÚutcÚyearÚmonthÚstr)rM   ÚyÚmZ
start_yearr   r   r   Úcurrent_seasonã  s    rh   c                 C   s0  | dkr–d}|t v rt | S d}d}zZtj ||¡ tj|dd�}tj|d dd	d
�|d< |jddddddœd�}|t |< |W S    d t |< Y d S 0 | |f}|t v r®t | S d|› d| › d�}d| › d|› d�}zBtj ||¡ tj|dd�}tj|d dd	d
�|d< |t |< |W S    d t |< Y d S 0 d S )Nr"   )r"   Úallz+https://www.football-data.co.uk/new/USA.csvzE:\soccer\data\_tmp_MLS.csvz	utf-8-sig©r
   ÚDateTZcoerce)Zdayfirstr   ÚHomeTeamÚAwayTeamÚFTHGÚFTAGÚFTR)ÚHomeÚAwayZHGZAGZRes)Úcolumnsz(https://www.football-data.co.uk/mmz4281/ú/ú.csvzE:\soccer\_tmp_Ú_)Ú_season_cacheÚurllibZrequestZurlretrieveÚpdÚread_csvÚto_datetimeÚrename)ÚdivÚseasonÚkeyÚurlÚtmpÚdfr   r   r   Úfetch_season_dfê  sB    þ
rƒ   >	   ZacZvsZtheZclubZfcZafcZcfZ	deportivoZscc                    s\   ddl }ddl‰ | stƒ S ˆ  dt| ƒ¡}d ‡ fdd„|D ƒ¡ ¡ }dd„ | d	|¡D ƒS )
zÎWord tokens for fuzzy fixture matching, accent-folded, noise removed.
    Deliberately the same shape as maps._tokens -- substring matching on
    space-stripped names caused three separate wrong-club bugs.r   NZNFKDr   c                 3   s   | ]}ˆ   |¡s|V  qd S rW   )Z	combining)r)   Úc©Úunicodedatar   r   Ú	<genexpr>-  r,   z_name_tokens.<locals>.<genexpr>c                 S   s$   h | ]}t |ƒd kr|tvr|’qS )r'   )ÚlenÚ
_NAME_STOP©r)   Útr   r   r   Ú	<setcomp>.  s   ÿz_name_tokens.<locals>.<setcomp>z
[^a-z0-9]+)Úrer†   ÚsetZ	normalizere   ÚjoinÚlowerr   )Úsr�   ZnfkdZflatr   r…   r   Ú_name_tokens$  s    r’   c                 C   s0   | d u s|d u rd S | |kr dS || kr,dS dS )NÚHÚAÚDr   )ÚhgÚagr   r   r   Ú_ftr_from_goals2  s    r˜   c              
   C   s  �zÀddl }|  d¡pd ¡ }t |¡}|s2W dS | |  d¡¡| |  d¡¡ }}|r\|sbW dS z"t t| d ƒdd	… ¡ 	¡ }W n t
yš   Y W dS 0 | ||||¡| ||||¡g}|D ]ü}tjd
|› �ddidd�}	|	jdkrêqÀ|	 ¡  d¡� püi  d¡�pg }
t|
ƒdk�rqÀtdd„ |
D ƒƒ�r6 W dS dd„ |
D ƒ}dd„ |
D ƒ}t|ƒdk�snt|ƒdk�rv W dS |d d  dd¡d  ¡ }||k�r¤ W dS ||k�r¶ W dS  W dS W dS  t
�y } z4td |  d¡› d!|  d¡› d"|› �ƒ W Y d}~dS d}~0 0 dS )#aÂ  Final result from Kalshi settlement. Returns (ftr, score) or (None, '').

    Kalshi has no scoreline, so `score` comes back '' -- callers must treat it
    as cosmetic. Requires ALL THREE legs finalized with an unambiguous yes/no,
    and exactly one `yes`. Anything else (a `scalar` result, two winners, a
    partially-settled event) falls through: observed on 2026-08-07 that one
    settled event returned result='scalar' on all three legs.
    r   Nr}   r   ©Nr   ÚhomeÚawayÚdateé
   z5https://api.elections.kalshi.com/trade-api/v2/events/Zwith_nested_marketsÚtrueé   ©ÚparamsÚtimeoutéÈ   ZeventÚmarketsé   c                 s   s   | ]}|  d ¡dvV  qdS )Ústatus)Z	finalizedÚsettledN©r:   ©r)   rg   r   r   r   r‡   Y  r,   z&fetch_result_kalshi.<locals>.<genexpr>c                 S   s&   g | ]}t | d ¡ƒ ¡ dkr|‘qS )ÚresultÚyes©re   r:   r�   r©   r   r   r   Ú
<listcomp>[  r,   z'fetch_result_kalshi.<locals>.<listcomp>c                 S   s&   g | ]}t | d ¡ƒ ¡ dkr|‘qS )rª   Znor¬   r©   r   r   r   r­   \  r,   r   rF   Útickerú-éÿÿÿÿ©r“   r   ©r”   r   ©r•   r   z  [Kalshi resolve] ú v ú: )Úkalshi_liver:   Úupperr(   Úleague_kalshi_seriesÚget_abrr   Úfromisoformatre   rœ   Ú	ExceptionÚbuild_event_tickerÚrequestsÚstatus_coder@   rˆ   ÚanyÚrsplitÚprint)ÚbetZKLVr}   ÚseriesZhaZaaÚgdZtickersZtkÚrr¤   ZwinnersZlosersÚsuffixÚer   r   r   Úfetch_result_kalshi8  sT    	
""
ÿþ
 

&rÈ   c              
   C   s2  zÞddl }| |  d¡pd ¡ |  d¡|  d¡¡}|s<W dS | d¡rP| d	¡rVW dS | d
¡pv| d¡pli  d
¡pvd}t|ƒ d¡}t|ƒdkr˜W dS t|d  ¡ ƒt|d  ¡ ƒ }}t	||ƒ}|rÚ||› d|› �fndW S  t
�y, } z4td|  d¡› d|  d¡› d|› �ƒ W Y d}~dS d}~0 0 dS )aK  Final result from Poly-US event state. Returns (ftr, score) or (None, '').

    Poly-US carries a real scoreline ('0-1') plus `ended`/`live`, verified live
    on 2026-08-07 against API-Football mid-match. Requires `ended` -- a score
    read while `live` is still true would settle the bet at a partial score.

    LIMITATION (measured 2026-08-07): /v2/leagues/<slug>/events lists only
    UPCOMING fixtures -- a finished match drops off within hours. So this fires
    only for games that ended very recently, and returns None for anything the
    nightly resolver is typically looking at. It is kept in the chain because
    it costs nothing when it misses and it is the only free source of an actual
    SCORELINE; capturing the score at FT during live monitoring (rather than at
    resolution time) is what would make it dependable.
    r   Nr}   r   rš   r›   r™   ZendedZliveÚscoreZ
eventStater¯   rF   r   z  [Poly-US resolve] r´   rµ   )Zpoly_us_pricesZ
find_eventr:   r·   re   r   rˆ   Úintr   r˜   r»   rÁ   )rÂ   ZPUSrÇ   ÚrawÚpartsr–   r—   Úftrr   r   r   Úfetch_result_polyusk  s&    ÿ""
&rÎ   c              
      sÆ  �zpt  ¡  |  d¡pd ¡ ¡p"i  d¡}|s4W dS t|  d¡ƒt|  d¡ƒB }t|ƒdk rbW dS d}d	D ]ª}tjd
|dd|dœdd�}| ¡ }t|t	ƒrž|s¤ �q|D ]R}t
|  d¡ƒdd… t
| dd¡ƒvrÔq¨t| d¡ƒ}t||@ ƒdkr¨|} qüq¨|�st|ƒdk rj �qqj|�r(| d¡�s.W dS g }	| d¡�p@g D ]z}
zt |
 d¡�pZd¡}W n t�yz   Y �qBY n0 |�rBt|d ƒdk�rB|	 |
 d¡�p²|
 d¡�p²d ¡ ¡ �qBt|	ƒdk�rÒW dS |	d ‰ t
|  d¡ƒ ¡ t
|  d¡ƒ ¡  }}dˆ v �sdˆ v �rW dS t‡ fd d!„| ¡ dd… D ƒƒ�rDW d"S t‡ fd#d!„| ¡ dd… D ƒƒ�rnW d$S W dS  t�yÀ } z4td%|  d¡› d&|  d¡› d'|› �ƒ W Y d}~dS d}~0 0 dS )(at  Final result from Polymarket.com resolution. Returns (ftr, score) or (None, '').

    A resolved gamma market prices its winning outcome at 1 and the rest at 0.
    Requires the event closed with exactly one outcome at 1 across the three
    match-winner markets, so a partially-resolved or disputed event falls
    through rather than resolving on a stale mid-price.
    r}   r   Zpoly_tagr™   rš   r›   rF   N)r   éd   r£   z'https://gamma-api.polymarket.com/eventsrž   rÏ   )Ztag_slugÚclosedÚlimitÚoffsetrŸ   r    rœ   r�   ZslugÚtitlerÐ   r¤   ZoutcomePricesz[]r   g®Gáz®ï?ZgroupItemTitleZquestionr   ZdrawZtier³   c                 3   s"   | ]}t |ƒd kr|ˆ v V  qdS ©r¥   N©rˆ   rŠ   ©Zlabelr   r   r‡   Ã  r,   z'fetch_result_polycom.<locals>.<genexpr>r±   c                 3   s"   | ]}t |ƒd kr|ˆ v V  qdS rÔ   rÕ   rŠ   rÖ   r   r   r‡   Å  r,   r²   z  [Poly.com resolve] r´   rµ   )r(   Zleague_map_fullr:   r·   r’   rˆ   r½   r@   Ú
isinstanceÚlistre   Úloadsr»   r?   Úappendr�   r¿   r   rÁ   )rÂ   ÚtagZwantrÇ   rÒ   rÅ   ZbatchZcandZgotÚwonrg   ZpricesZhome_lZaway_lr   rÖ   r   Úfetch_result_polycomŽ  sf    &ÿþ&*&$$&rÝ   c              
   C   sè   | sdS z t dd| iƒ}|s"W dS |d }|d d d }|dvrHW dS |d	 d
 }|d	 d }|du sp|du rvW dS ||kr„d}n||kr’d}nd}||› d|› �fW S  tyâ } z"td| › d|› �ƒ W Y d}~dS d}~0 0 dS )zWFetch final result from API-Football by fixture ID. Returns (ftr, score) or (None, '').r™   ÚfixturesÚidr   Úfixturer¦   Úshort©ZFTZAETZPENZgoalsrš   r›   Nr“   r”   r•   r¯   z  [AF resolve] fixture rµ   ©Úaf_getr»   rÁ   )Ú
fixture_idÚresultsÚfixr¦   r–   r—   rÍ   rÇ   r   r   r   Úfetch_result_afÍ  s.    rè   Z	PostponedZ	CancelledZ	AbandonedZWalkoverZAwarded)ZPSTZCANCZABDZWOZAWDc              
   C   sv   | sdS z.t dd| iƒ}|s"W dS |d d d d W S  typ } z"td| › d	|› �ƒ W Y d}~dS d}~0 0 dS )
zžReturn the API-Football short status (e.g. 'FT','PST','CANC') or None.
    Used to distinguish a void/postponed match from one whose result just isn't in yet.NrÞ   rß   r   rà   r¦   rá   z  [AF status] fixture rµ   rã   )rå   ræ   rÇ   r   r   r   Úfetch_status_afó  s    ré   c              	      s´  |   dd¡dd… }|sdS tj t|› d�¡}tj |¡s@dS |tvrtztj|dd�t|< W n t	yr   Y dS 0 t| }||d	  
g d
¢¡ }|jrœdS | d  ¡ ‰| d  ¡ ‰ |d j ¡ ˆk|d j ¡ ˆ k@ }|| }|j�r(|d j ¡  ‡fdd„¡|d j ¡  ‡ fdd„¡@ }|| }|j�r4dS |jd }|d |d  }}	zt|ƒt|	ƒ }}	W n ttf�y€   Y dS 0 ||	k�r�dn|	|k�rždnd}
|
|› d|	› �fS )zWLook up final result from live_data/YYYY-MM-DD.csv. Returns (ftr, score) or (None, '').rœ   r   Nr�   r™   ru   r   rj   r¦   râ   rš   r›   c                    s   | ˆ v pˆ | v S rW   r   ©Úx)Úhome_nr   r   Ú<lambda>  r,   z(fetch_result_live_data.<locals>.<lambda>c                    s   | ˆ v pˆ | v S rW   r   rê   )Úaway_nr   r   rí     r,   r°   Z
home_goalsZ
away_goalsr“   r”   r•   r¯   )r:   Úosr   r�   ÚLIVE_DATA_DIRÚexistsÚ_live_data_cachery   rz   r»   ZisinÚemptyr�   re   ÚapplyÚilocrÊ   Ú
ValueErrorÚ	TypeError)rÂ   Údate_strr   r‚   ZftÚmaskÚrowsÚrowr–   r—   rÍ   r   )rî   rì   r   Úfetch_result_live_data  sD    $ÿ
 rü   c            &         sN  t ƒ } | sg S tƒ }tƒ }tƒ }tƒ }tƒ }t ¡ j}t ¡ j	dk rd|d › t
|d ƒdd … › �n|d › t
|ƒdd … › �}g }g }	t tj¡ ¡ }
| D �]‚}d }d}d }t|ƒ\}}|d urÆd}|d u ræt|ƒ\}}|d uræd}|d u �r
t|ƒ\}}|d u�r
d}|d u �r.t|ƒ\}}|d u�r.d}|d u �rXt| d	¡ƒ\}}|d u�rXd
}|�rŠtd| d¡› d| d¡› d|› d|› �ƒ |d u �rR|d }|dk�r¬dgn||g}|D �]–}t||ƒ}|d u �rÖ�q¸t|d ƒ‰t|d ƒ‰ zt |d d d… ¡}W n   d }Y n0 |d ˆk|d ˆ k@ }|d u�rXd|jv �rX||d |k@ }|| }|j�rÖ||d j
 ¡  ‡fdd„¡|d j
 ¡  ‡ fdd„¡@  }|d u�rÊd|jv �rÊ||d |k }|j�sÖ|}|j�râ�q¸|jd }| dd ¡}t |¡�r¸t
|ƒ ¡ dk�r¸t
|ƒ ¡ }t | dd¡ƒ› d t | d!d¡ƒ› �} �qR�q¸z t !|d d d… d"¡ ¡ }W n   |
}Y n0 |d u �r´t"| d	¡ƒ}|t#v �r|
| j$dk�ri |¥d#d d$t#| d||
 %¡ d%œ¥}| &|¡ | &|¡ td&|d › d'|d › d(t#| › d)�ƒ qœ|
| j$dk�r¨t'| d*d¡dƒ }t'|| dƒ}i |¥d+d,|||
 %¡ d-œ¥}| &|¡ | &|¡ td.|d › d'|d › d/|d0›d1|d2›�ƒ qœ|	 &|¡ qœ| d3d4¡}||d k}| d*¡}|�r|�rüt'|| d5d¡d  dƒn
t'|dƒ }t'|| dƒ}nd}t(| d6¡�o2| d7¡ƒ}t(| d8¡�oL| d9¡ƒ} |�r„| �r„t)|d9 ƒt)|d7 ƒd: k �r€d,}nd,} d }!|�rØd|d7  |d7  }"|�rºt'|d6 |" dƒnt'|d6 dƒ }!t'||! dƒ}d }#| �r,d|d9  |d9  }$|�rt'|d8 |$ dƒnt'|d8 dƒ }#t'||# dƒ}i |¥|||||
 %¡ |d;œ¥}|!d u�rd|!|d<< ||d=< |#d u�r~|#|d>< ||d?< | &|¡ | &|¡ |!d u�r°d@|!d0›dA|d2›�nd}%|%|#d u�rÔdB|#d0›dC|d2›�nd7 }%tdD|d › d'|d › d(|› dE|�rdFndG› dH|d0›d1|d2›|%› �ƒ qœt*|ƒ t+|ƒ t,|ƒ t-|	ƒ t.|ƒ |S )INra   rF   r   r   Z	live_dataZkalshiZpolyuszpoly.cområ   zapi-footballz  [resolve] rš   r´   r›   rµ   z via r}   r"   ri   rœ   r�   rl   rm   rk   c                    s   ˆ   ¡ | v p| ˆ   ¡ v S rW   ©r�   rê   )Úhome_fr   r   rí   z  r,   z&resolve_pending_bets.<locals>.<lambda>c                    s   ˆ   ¡ | v p| ˆ   ¡ v S rW   rý   rê   )Úaway_fr   r   rí   {  r,   r°   rp   rn   r   r¯   ro   ú%Y-%m-%dZVOIDT)rÍ   rÜ   ÚvoidedÚvoid_reasonÚpnlÚbankroll_afterÚresolved_atz  VOID: ú vs ú (z) -- stake returned, no P&LÚstakeZEXPF)rÍ   rÜ   r  r  r  z  EXPIRED: z (no result after 10d) P&L=ú+.2fz -> Bankroll=ú,.2frÂ   rr   Úb365Úkalshi_stakeÚ
kalshi_askÚ
poly_stakeÚpoly_askg•Ö&è.>)rÍ   rÜ   r  r  r  rÉ   Ú
kalshi_pnlZkalshi_bankroll_afterÚpoly_pnlZpoly_bankroll_afterz | Kalshi P&L=z -> K.Bankroll=z | Poly P&L=z -> P.Bankroll=ú  Resolved: z) ZWINZLOSSz P&L=)/rY   rC   rR   rU   r^   rh   r   rM   rc   rd   re   r   rb   rœ   rü   rÈ   rÎ   rÝ   rè   r:   rÁ   rƒ   r8   ry   r{   rs   ró   r�   rô   rõ   Znotnar   rÊ   Ústrptimeré   ÚVOID_STATUSESZdaysÚ	isoformatrÚ   rL   Úboolr?   rP   rS   rV   r\   r`   )&Úpendingr=   Úkalshi_bankrollÚpoly_bankrollr_   r~   rf   Zprior_seasonÚresolvedZstill_pendingÚtodayrÂ   rÍ   rÉ   Úsrcr}   Zseasons_to_tryr‘   r‚   Zbet_daterù   Z
match_rowsZfzrû   rÅ   ZvstatusZresult_entryr  Úbet_siderÜ   Z
b365_stakeZk_okZp_okr  Zk_payoutr  Zp_payoutZk_strr   )rÿ   rþ   r   Úresolve_pending_bets)  s<   
J






,




ÿÿ
&
 

ý

ÿ
ÿ

ÿÿ


,((þ



"&ÿÿÿÿ
r  c                 C   s:   d|  d|  d|  }d|  | d| | d| | fS )Nr   r   )Úhr*   ÚaZoverr   r   r   Úremove_marginó  s    r!  c                   C   s   t dd„ tƒ D ƒƒS )z0Total stakes tied up in unresolved pending bets.c                 s   s   | ]}|  d d¡V  qdS ©r  r   Nr¨   ©r)   Úbr   r   r   r‡   ù  r,   z#committed_amount.<locals>.<genexpr>)ÚsumrY   r   r   r   r   Úcommitted_amount÷  s    r&  r¥   c                 C   sÀ   t › d| › �}t|ƒD ]¤}z0tj|t|dd�}| ¡  | ¡  dg ¡W   S  ty¸ } zV||d k rvt 	d| ¡ n.t
d| › d|› d	|› �ƒ g W  Y d
}~  S W Y d
}~qd
}~0 0 qd
S )zAPI-Football GET with retry.rt   é   )Zheadersr¡   r¢   Zresponser   rF   z  [AF] Error ú rµ   N)ÚAF_BASEÚranger½   r:   Ú
AF_HEADERSZraise_for_statusr@   r»   ÚtimeÚsleeprÁ   )Zendpointr¡   Zretriesr€   ÚattemptrÅ   rÇ   r   r   r   rä   û  s    rä   c            8      C   sn  t  tj¡ ¡ } |  ¡ g}g }d}tD �]x}t |¡}|s<q&t 	|t
t¡}|D �]N}td|||dœƒ}|d7 }|D �](}	|	d d }
|	d d dd	… }|	d d d
d… }d } }}d } }}|	 dg ¡D ]¢}| dg ¡D ]�}|d dkròqàdd„ |d D ƒ}| d¡}| d¡}| d¡}t|||gƒ�s4qà|d tk�rT|||  }}}qà|d tkrà|||  }}}qàqÐ|rr|rr| |
|||||||||dœ
¡ qrqNq&dh}dd„ |D ƒ}d}tj |¡�r|  ¡ }t|dd���&}|D �]}| ¡ }| d¡�r|dd…  ¡ }�qä|�rä| d¡�r(�qäd d!„ | d"¡D ƒ}t|ƒdk �rN�qä|d |d |d# |d$ |d% f\} }!}"}#}$|"|v�sä|$ ¡ �s’�qä||  ¡ k�r¤�qät|$ƒ}%|%|v �rº�qä| |%¡ tdd|%iƒ}&|d7 }|&D �]}	d }' }(})d }* }+},|	 dg ¡D ]®}| dg ¡D ]š}|d dk�r.�qd&d„ |d D ƒ}| d¡}| d¡}| d¡}t|||gƒ�sr�q|d tk�r’|||  }'}(})n|d tk�r|||  }*}+},�q�q|'�rÞ|*�rÞ| |%|  ¡ |#|"| |!|'|(|)|*|+|,d'd(œ¡ �qÞ�qäW d  ƒ n1 �s0    Y  td)|› d*t|ƒ› �ƒ |�s8g S d+d!„ |D ƒ}-tt  |-¡ƒ}.i }/d,}0t!dt|.ƒ|0ƒD ]v}1|.|1|1|0 … }2d- "d.d/„ |2D ƒ¡}3td0d1|3iƒ}|D ]8}	|	d d }
|	d2 d3 d |	d2 d4 d d5œ|/|
< �q¦�qlt#ƒ }4g }5|D ]v}6|6d6 }
|
|4v �r�qò|6 d7¡�s&|
|/v�r&�qò|4 |
¡ |6 d7¡�s\|/|
 }7|7d3 |6d3< |7d4 |6d4< |5 |6¡ �qò|5S )8z�
    Pull today's fixtures + Pinnacle/B365 odds from API-Football.
    Returns list of dicts with all fields needed for CLV calculation.
    r   Zodds)Úleaguer~   rœ   r   rà   rß   rœ   Nr�   é   é   Z
bookmakersr[   r;   zMatch Winnerc                 S   s   i | ]}|d  t |d ƒ“qS ©ÚvalueZodd©r?   ©r)   r   r   r   r   r+   +  r,   z,fetch_fixtures_with_odds.<locals>.<dictcomp>Úvaluesrq   ZDrawrr   )
rå   rœ   r,  r}   Úpin_hÚpin_dÚpin_aÚb365_hÚb365_dÚb365_ar"   c                 S   s   h | ]}|d  ’qS )rå   r   ©r)   rÅ   r   r   r   rŒ   ?  r,   z+fetch_fixtures_with_odds.<locals>.<setcomp>zE:\soccer\data\watchlist.txtr   rj   zDATE=é   r   c                 S   s   g | ]}|  ¡ ‘qS r   )r   )r)   Úpr   r   r   r­   K  r,   z,fetch_fixtures_with_odds.<locals>.<listcomp>ú|rF   r¥   r'   c                 S   s   i | ]}|d  t |d ƒ“qS r2  r4  r5  r   r   r   r+   `  r,   T)rå   rœ   r,  r}   rš   r›   r7  r8  r9  r:  r;  r<  Ú_name_from_wlz  API calls: z | Fixtures with Pin+B365: c                 S   s   g | ]}|  d ¡s|d ‘qS )rA  rå   r¨   r=  r   r   r   r­   {  r,   rŸ   r¯   c                 s   s   | ]}t |ƒV  qd S rW   )re   )r)   r   r   r   r   r‡   �  r,   z+fetch_fixtures_with_odds.<locals>.<genexpr>rÞ   ZidsZteamsrš   r›   )rš   r›   rå   rA  )$r   rM   r   rb   rœ   r  ÚLEAGUESr(   Zleague_api_idZleague_current_seasonr+  Ú	AF_SEASONrä   r:   ri   Ú	AF_PIN_IDÚ
AF_B365_IDrÚ   rï   r   rñ   r   r   r   r   rˆ   ÚisdigitrÊ   ÚaddrÁ   rØ   ÚdictÚfromkeysr*  r�   rŽ   )8r  ZdatesrË   Ztotal_callsr}   Z	league_idr~   rø   ræ   rç   ZfidZfdateZftimer7  r8  r9  r:  r;  r<  ZbkrÂ   Zvalsr  Zd_r   ZFIXTURE_QUERY_DIVSZ	seen_fidsZwl_pathZwl_dateÚ_fÚ_linerÌ   Ú_homeZ_awayZ_lcZ_koZ_fid_sZ_fidZ_resultsZ_pin_hZ_pin_dZ_pin_aZ_b365_hZ_b365_dZ_b365_aZneeds_lookupZfidsZfixture_infoZ
chunk_sizeÚiÚchunkZids_strÚseenÚ
candidatesrÅ   Úinfor   r   r   Úfetch_fixtures_with_odds
  sø    




ú

,


÷0þ

rR  c           	      C   sˆ   ztddl m} ddlm} t | ¡ ¡}|s2W dS | |¡}|||| |ƒ}|rp| d¡durp|d | d¡fW S W dS    Y dS 0 dS )zIFetch Kalshi away price for a signal. Returns (mid, ask) or (None, None).r   )Úget_all_prices©rœ   ©NNZaway_midNZaway_ask)	r¶   rS  r   rœ   r(   r¸   r·   rº   r:   )	rš   r›   r}   Úgame_date_strrS  Údate_rÃ   rÄ   rª   r   r   r   Úfetch_kalshi_signalœ  s    
rX  c           	      C   s†   zrddl }ddlm} | |¡}|j|| || ¡ d�}|rn| |› d�¡durn||› d� | |› d�¡fW S W dS    Y dS 0 dS )z]Fetch Polymarket price for a signal side ('home'/'away'). Returns (mid, ask) or (None, None).r   NrT  )r/  Z_midZ_askrU  )Úpolymarket_liver   rœ   rº   rS  r·   r:   )	rš   r›   r}   rV  ÚsiderY  rW  rÄ   rª   r   r   r   Úfetch_poly_signal¬  s    
 r[  c           1      C   s¤  t | tƒ  dƒ}tdd„ tƒ D ƒƒ}t || dƒ}tƒ }tdd„ tƒ D ƒƒ}t || dƒ}tƒ }|slg dfS g }	|D �]$}
|
d |
d |
d   }}}t|||ƒ\}}}g }|
d	 d
|  d
|  }|
d d
|  d
|  }t|  kròtk �rn n| 	d|
d	 ||f¡ t
|  k�r$tk �r>n n| 	d|
d ||f¡ t|ƒdk�rÄt |dd„ d�}t|dd„ d�}td|
d › d|
d › d|d › d|d d d›d|d › d|d d d›d�ƒ |g}|D �]Î\}}}}|d
 }t || d
|  | dƒ}|dk�r�qÈt|t tƒ}t|| dƒ}|dk �r0�qÈd\}}d  }  }!}"|dk�rðt|
d |
d |
d! |
d" ƒ\}}|�rð|dk�rðd
| | }#t ||# d
|  |# dƒ}$t|$t tƒ}%t|%| dƒ}&|&dk�rð|&} t|$d#ƒ}!t|| | d d
ƒ}"d\}'}(d  }) }*}+|dk�rdnd},t|
d |
d |
d! |
d" |,ƒ\}'}(|(�rº|(dk�rºd
|( |( }-t ||- d
|  |- dƒ}.t|.t tƒ}/t|/| dƒ}0|0dk�rº|0})t|.d#ƒ}*t||( |( d d
ƒ}+|	 	|
d" |
d$ d% t |
d! |
d! ¡|
d! |
d& |
d |
d |t|dƒtd
| dƒt|d d
ƒt|d#ƒt|d d
ƒ|t|d#ƒd'|�rJt|d#ƒnd |�r\t|d#ƒnd |"|!| |'�rtt|'d#ƒnd |(�r†t|(d#ƒnd |+|*|)d(œ¡ �qÈqt|	d)fS )*zˆFetch today's fixtures from API-Football and scan for CLV signals.
    Away CLV >5% | Home CLV >8% (both vs Pinnacle closing fair).
    r   c                 s   s   | ]}|  d d¡V  qdS ©r  r   Nr¨   r#  r   r   r   r‡   ¾  r,   z scan_fixtures.<locals>.<genexpr>c                 s   s   | ]}|  d d¡V  qdS ©r  r   Nr¨   r#  r   r   r   r‡   Á  r,   Fr7  r8  r9  r<  r   r:  rr   rq   rF   c                 S   s   | d S ©Nr¥   r   rê   r   r   r   rí   Ú  r,   zscan_fixtures.<locals>.<lambda>)r   c                 S   s   | d S r^  r   rê   r   r   r   rí   Û  r,   ú  [DEDUPE] rš   r  r›   u   : both sides fired â€” keeping r  r¥   rÏ   ú+.1fz%) over z%)r%   rU  Nr}   rœ   r'   r,  z UTCrå   zAPI-Football/Pinnacle)rœ   r,  r/  r}   rå   rš   r›   rÂ   r  ÚfairÚedge_pctÚkelly_fÚ	kelly_pctr  Úp_winÚ
pin_sourceÚ
kalshi_midr  Úkalshi_edge_pctÚkalshi_kelly_fr  Úpoly_midr  Úpoly_edge_pctÚpoly_kelly_fr  T)Úmaxr&  r%  rY   rU   rR  r!  ÚEDGE_THRESHOLD_AWAYÚEDGE_UPPER_CAP_AWAYrÚ   ÚEDGE_THRESHOLD_HOMEÚEDGE_UPPER_CAP_HOMErˆ   ÚminrÁ   Ú
KELLY_MULTÚMAX_BET_PCTrL   rX  r[  ÚLEAGUE_NAMESr:   )1r=   r  Ú	availableZkalshi_pendingZkalshi_availabler  Zpoly_pendingZpoly_availablerP  ÚsignalsrÅ   r7  r8  r9  Ztp_hZtp_dZtp_aZbets_to_checkZedge_aZedge_hÚwinnerZloserr  r  r?  Zedger$  rc  Z
stake_fracr  rg  r  r  ri  rh  Zk_bZ	k_kelly_fZk_stake_fracZk_stakerj  r  r  rl  rk  Z	poly_sideZp_bZ	p_kelly_fZp_stake_fracZp_staker   r   r   Úscan_fixtures¹  sÂ    
ÿÿþþ



"

$


æry  c                 C   sŽ  t ƒ }tƒ }tƒ }tdd„ |D ƒƒ}tdd„ |D ƒƒ}tdd„ |D ƒƒ}|s|d|d›d|d›d|d›d|d›d	t|ƒ› d
�S tdd„ |D ƒƒ}tdd„ |D ƒƒ}	tdd„ |D ƒƒ}
tdd„ |D ƒƒ}tdd„ |D ƒƒ}tdd„ |D ƒƒ}|dkrü|
| d nd}|
dk�rdnd}|dk�r dnd}|dk�r2dnd}d|d›d|d›dt|ƒ› d|d›d|d›d|› d|	› d|› d|d›d|› d|d›d �S )!z1Build a small status bar showing bankroll health.c                 s   s   | ]}|  d d¡V  qdS r"  r¨   r#  r   r   r   r‡   1  r,   z(bankroll_summary_html.<locals>.<genexpr>c                 s   s$   | ]}|  d ¡r|  d d¡V  qdS r\  r¨   r#  r   r   r   r‡   2  r,   c                 s   s$   | ]}|  d ¡r|  d d¡V  qdS r]  r¨   r#  r   r   r   r‡   3  r,   zƒ<div style="background:#f0f0f0;padding:10px;margin-bottom:15px;border-radius:5px;font-size:12px">
        <b>Kalshi Bankroll:</b> $r
  z ($z- committed) |
        <b>Poly Bankroll:</b> $z& committed) |
        <b>Pending:</b> z6 bets | <b>Record:</b> 0W-0L | <b>P&L:</b> $0.00</div>c                 s   s   | ]}|  d ¡rdV  qdS ©rÜ   r   Nr¨   ©r)   r  r   r   r   r‡   9  r,   c                 s   s&   | ]}|  d ¡s|  d¡sdV  qdS )rÜ   r  r   Nr¨   r{  r   r   r   r‡   :  r,   c                 s   s   | ]}|  d d¡V  qdS ©r  r   Nr¨   r{  r   r   r   r‡   ;  r,   c                 s   s(   | ] }|  d ¡dur|  d d¡V  qdS ©r  Nr   r¨   r{  r   r   r   r‡   <  r,   c                 s   s(   | ] }|  d ¡dur|  d d¡V  qdS ©r  Nr   r¨   r{  r   r   r   r‡   =  r,   c                 s   s   | ]}|  d d¡V  qdS r"  r¨   r{  r   r   r   r‡   >  r,   r   rÏ   ú#2d7d2dú#cc3333zv<div style="background:#f0f0f0;padding:10px;margin-bottom:15px;border-radius:5px;font-size:12px">
    <b>Kalshi:</b> $z | <b>Poly:</b> $z |
    <b>Pending:</b> z bets (Kalshi $z	 / Poly $z) |
    <b>Record:</b> zW-z.L |
    <b>Kalshi P&L:</b> <span style="color:z">$z+,.2fz2</span> |
    <b>Poly P&L:</b> <span style="color:z</span></div>)r^   rY   rU   r%  rˆ   )r=   r  r_   r  r  Ú	committedZkalshi_committedZpoly_committedÚwinsÚlossesÚb365_pnlr  r  Ztotal_stakedZroiZ	pnl_colorÚk_colorÚp_colorr   r   r   Úbankroll_summary_html,  sf    ÿÿþþýÿÿþþþýýüüûûr‡  c           B   !   C   sJ  t ||ptƒ ƒ}d}|�rúdd„ |D ƒ}dd„ |D ƒ}	dd„ |D ƒ}
tdd„ |D ƒƒ}tdd„ |D ƒƒ}td	d„ |D ƒƒ}|| }|d
krŽdnd}d}|D �]ö}| dd¡}| d¡�r|d|d › d| dd¡› d|› d|d › d|d › d| dd¡› d�7 }qš| d¡�rdnd}| d¡�r(dnd}| dd
¡}| d ¡}| d!¡}||d u�r\|nd
 |d u�rn|nd
 }|d
k�r‚dnd}| d"¡�rž| d#d¡nd}g }| d"¡�rÄ| d$|d%›�¡ |d u�rà| d&|d%›�¡ |d u�rü| d'|d%›�¡ t|ƒd(k�r*|› |d)›d*�d+ |¡ d, }n|› |d)›�}|d|d › d| dd¡› d|› d|d › d|d › d|› d-|› d.|› d/|› d.|› d0�7 }qš|
�rªd1t|
ƒ› d2�nd}d3t|ƒ› d4t|	ƒ› d|› d5|› |d)›d6|› d7|d8›d9|d8›d:tƒ d8›d;�}| �s*d<|› d=|› d>|› d?|�pd@› dAt› dB�S tdCd„ | D ƒƒ}tdDd„ | D ƒƒ}zÀt	t
ƒ�} t | ¡}!W d   ƒ n1 �sz0    Y  dEdF„ |!D ƒ}"| D ]t}#dG|# dHdI¡ ¡ v �r´dnd}$|" |# dJ¡|$f¡}%|%�r–|# dK¡�s–|% dL¡|#dK< |% dM¡|#dN< |% dO¡|#dP< �q–W n ttf�y&   Y n0 d}&| D �]Ž}'dQ}(d})|' dR¡�r4|' dS¡�pXd
d
k�rddnd}*|' dT¡�r„dU|'dT d)›�ndV}+|' dW¡},|' dS¡}-|,d u�r´dX|,dY›d+�nd}.|-d u�rÐdZ|-d[›d\�nd}/d]|*› d^|'dR dY›d+|.› |/› d_|+› d`�
}(|' da¡d u�r4tt|'da t tƒdb dcƒ}0dU|0d)›�})dQ}1|' dd¡�rö|' de¡�pRd
d
k�r^dnd}2|' df¡�r~dU|'df d)›�ndV}3|' dg¡}4|' de¡}5|4d u�r®dX|4dY›d+�nd}6|5d u�rÊdZ|5d[›d\�nd}7d]|2› d^|'dd dY›d+|6› |7› d_|3› d`�
}1|' dHdI¡}8|8 dh¡}9|' d#d¡}:|' did¡};|' djd¡}<|' dkd
¡}=|' dld¡}>|' d"d
¡}?|' dK¡�r–dm|' dNdn¡› do|' dPdn¡› dp|'dK d dq… › dr�}@nX|' ds¡tv �r¸|' dT¡�r¸dt}@n6t�rêtd u �sØ|' ds¡tv �rê|' dT¡�rêdu}@ndv}@|&d|'d › d|' dwd¡› d|' dd¡› d|9�r6dx|'d  dy |'d  n|'d dz |'d  d` › d|:› d|;› d{|<› |<dk�rnd\nd› d||=d} d~›d|>› |>dk�r’d\nd› d€|?d)›d||(› d|)› d|1› d|@› d��7 }&�q0|�rÖd‚|d8›d`�nd}Ad<|› d=|› dƒt| ƒ› d„t| ƒd(k�rd…nd› d†|› d‡|d8›dˆ|| d} d~›d‰|A› dŠ|&› d‹t› dŒtd} d�›dŽ�S )�Nr   c                 S   s   g | ]}|  d ¡r|‘qS )rÜ   r¨   r=  r   r   r   r­   S  r,   zbuild_html.<locals>.<listcomp>c                 S   s$   g | ]}|  d ¡s|  d¡s|‘qS )rÜ   r  r¨   r=  r   r   r   r­   T  r,   c                 S   s   g | ]}|  d ¡r|‘qS )r  r¨   r=  r   r   r   r­   U  r,   c                 s   s   | ]}|  d d¡V  qdS r|  r¨   r=  r   r   r   r‡   \  r,   zbuild_html.<locals>.<genexpr>c                 s   s(   | ] }|  d ¡dur|  d d¡V  qdS r}  r¨   r=  r   r   r   r‡   ]  r,   c                 s   s(   | ] }|  d ¡dur|  d d¡V  qdS r~  r¨   r=  r   r   r   r‡   ^  r,   r   ú+ÚstrategyZbaseliner  z
        <tr>
          <td>rœ   z</td>
          <td>r/  rš   r  r›   zm</td>
          <td></td>
          <td style="color:#888"><b>VOID</b></td>
          <td style="color:#888">r  Zvoidz$ (stake returned)</td>
        </tr>rÜ   ÚWÚLr  r€  r  r  r  r  r  zB:r	  zK:zP:r   ú.2fr  r(  ú)z!</td>
          <td style="color:z"><b>z%</b></td>
          <td style="color:z</b></td>
        </tr>z / z voidz
    <h3>Results (zW / z, a  )</h3>
    <table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;margin-bottom:20px">
      <tr style="background:#444;color:white">
        <th>Date</th><th>League</th><th>Strategy</th><th>Match</th><th>Odds</th><th>Result</th><th>P&L</th>
      </tr>
      z0
    </table>
    <p><b>Updated B365 bankroll: $r
  z | Kalshi bankroll: $z | Poly bankroll: $z'</b></p>
    <hr style="margin:20px 0">zJ
    <html><body style="font-family:Arial,sans-serif;font-size:13px">
    z
    z
    <h2>No CLV Signals - z </h2>
    <p style="color:grey">zNo qualifying Away bets found.zZ</p>
    <p style="color:grey;font-size:11px">
      Strategy: Away CLV >5% vs Pinnacle | z#x Kelly
    </p>
    </body></html>c                 s   s   | ]}|  d d¡V  qdS r"  r¨   ©r)   r‘   r   r   r   r‡   ¨  r,   c                 s   s    | ]}|  d ¡r|d  V  qdS )r  Nr¨   rŽ  r   r   r   r‡   ©  r,   c                 S   sF   i | ]>}|  d ¡d|  dd¡ ¡ v s6|  dd¡ d¡r:dndf|“qS )rå   ÚAWAYÚclient_order_idr   z-awayr›   rš   )r:   r·   Úendswith©r)   rÇ   r   r   r   r+   °  s   üÿýzbuild_html.<locals>.<dictcomp>r�  rÂ   rr   rå   Zauto_order_idÚorder_idÚcountZ
auto_countÚprice_centsZauto_price_centszN/Ar  rh  r  ú$r¯   rg  zmid=ú.3fzedge=r`  ú%z<span style="color:z">z</span><br><b>z</b>ri  iè  rF   r  rk  r  rj  r“   ra  rb  re  rd  z#<span style="color:#2d7d2d">PLACED ú?ú@z-c<br><span style="font-size:10px;color:grey">r&   z</span></span>r}   z(<span style="color:#cc6600">PAPER</span>z*<span style="color:#cc3333">SKIPPED</span>z!<span style="color:#aaa">-</span>r,  z<b>z</b> vs z vs <b>z</td>
          <td><b>z</b></td>
          <td>rÏ   z.1fz%</td>
          <td>z</td>
          <td><b>$z</td>
        </tr>z | Kalshi total: <b>$z	
    <h2>z CLV Signalr‘   ú - z#</h2>
    <p>B365 total stake: <b>$z</b> (z% of bankroll)a½  </p>

    <table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;width:100%">
      <tr style="background:#222;color:white">
        <th>Date</th><th>Time</th><th>League</th><th>Match</th>
        <th>B365A</th><th>Fair</th><th>Edge</th><th>P(win)</th>
        <th>Kelly%</th><th>B365 Stake</th><th>Kalshi (ask/edge/stake)</th><th>Kalshi @$1k</th><th>Poly (ask/edge/stake)</th>
        <th>Auto</th>
      </tr>
      zj
    </table>

    <p style="color:grey;font-size:11px;margin-top:20px">
      Away CLV >5% vs Pinnacle | zx Kelly | Max z.0fz%% per bet
    </p>
    </body></html>)r‡  rR   r%  r:   rÚ   rˆ   r�   rU   rs  r   ÚAUTO_TRADE_LOGr@   rA   r·   r   rö   rL   rr  rt  r   ÚAUTO_TRADE_EXCLUDE_LEAGUESÚAUTO_TRADE_ENABLEDÚAUTO_TRADE_LEAGUES)Brw  r  r=   r  r  ÚreasonZ
status_barZresolved_htmlr‚  rƒ  ZvoidsZnetZ
kalshi_netZpoly_netZ	total_netZsignZres_rowsrÅ   r‰  ZiconZcolorr„  Zk_pnlZp_pnlZcombinedZpnl_signZ	odds_cellrÌ   Zpnl_strZvoid_strZtotal_stakeZtotal_kalshi_stakerJ  Z	_auto_logZ_auto_by_keyÚ_sZ	_bet_normZ_recrú   r‘   Zk_cellZfixed_k_strr…  Zk_stake_strZk_midZk_edgeZ	k_mid_strZ
k_edge_strZfixed_k_stakeZp_cellr†  Zp_stake_strZp_midZp_edgeZ	p_mid_strZ
p_edge_strZ	bet_labelZis_homeZb365_valZfair_valZedge_valZ	p_win_valZkelly_pct_valZ	stake_valZ	auto_cellZkalshi_liner   r   r   Ú
build_htmlO  sÂ   
þ
ýüûû
ø



(

 þ
ýüûûúùùøø
ÿÿÿÿÿúøøø
þýüûù
*ü
 

ÿÿ 

ÿÿ

ÿþ
,þ
ý
ü4ûúùøø	÷
ö
öõôóòñþýüüüû
ûûòîîr¢  c              
   C   sÎ   t rtstdƒ dS z€tdƒ}| |d< t |d< t|d< | t|dƒ¡ t dd	¡�.}| 	t t¡ | 
t t| ¡ ¡ W d   ƒ n1 s†0    Y  W d
S  tyÈ } ztd|› �ƒ W Y d }~dS d }~0 0 d S )Nz0  Email not configured (no .env). Skipping send.FZalternativeZSubjectZFromZToÚhtmlzsmtp.gmail.comiÑ  Tz  EMAIL FAILED: )ÚEMAIL_ADDRESSr   rÁ   r   r   Zattachr   ÚsmtplibZSMTP_SSLZloginZsendmailZ	as_stringr»   )ÚsubjectZ	html_bodyÚmsgZserverrÇ   r   r   r   Ú
send_email  s     0r¨  c                  C   sN   z8t tƒ�} t | ¡W  d   ƒ W S 1 s,0    Y  W n   i  Y S 0 d S rW   )r   ÚSENT_LOGr@   rA   rB   r   r   r   Ú	load_sent)  s
    
.rª  c                 C   s>   t tdƒ� }tj| |dd� W d   ƒ n1 s00    Y  d S rZ   )r   r©  r@   rK   ©Údatar   r   r   r   Ú	save_sent0  s    r­  c                 C   s    | d › d| d › d| d › �S )Nrœ   r@  rš   r›   r   ©r‘   r   r   r   Ú
signal_key4  s    r¯  c                    s4   t ƒ }t| ˆ g ¡ƒ‰‡ ‡fdd„| D ƒ}|ˆ|fS )Nc                    s4   g | ],}|  d ¡s,|  d¡ˆ ks,t|ƒˆvr|‘qS )Ú	_reminderrœ   )r:   r¯  rŽ  ©r  Ú
today_sentr   r   r­   ;  r,   zfilter_new.<locals>.<listcomp>)rª  rŽ   r:   )rw  r  ÚsentÚnewr   r±  r   Ú
filter_new7  s    rµ  c                    sR   |  dd„ | D ƒ¡ t|ƒˆ |< tˆ  ¡ ƒdd … }‡ fdd„|D ƒ‰ tˆ ƒ d S )Nc                 s   s   | ]}t |ƒV  qd S rW   )r¯  rŽ  r   r   r   r‡   ?  r,   zmark_sent.<locals>.<genexpr>iùÿÿÿc                    s   i | ]}|ˆ | “qS r   r   ©r)   r   ©r³  r   r   r+   B  r,   zmark_sent.<locals>.<dictcomp>)ÚupdaterØ   ÚsortedÚkeysr­  )rw  r  r²  r³  Úcutoffr   r·  r   Ú	mark_sent>  s
    r¼  c               	   C   sX   z8t tƒ�} t | ¡W  d   ƒ W S 1 s,0    Y  W n ttfyR   g  Y S 0 d S rW   )r   rœ  r@   rA   r   rö   rB   r   r   r   Ú_load_auto_logF  s
    
.r½  c                 C   s>   t tdƒ� }tj| |dd� W d   ƒ n1 s00    Y  d S rZ   )r   rœ  r@   rK   r«  r   r   r   Ú_save_auto_logN  s    r¾  c                 C   sö   dt  ¡ jdd�› d| › d�}z8ttdƒ�}| |¡ W d  ƒ n1 sJ0    Y  W n tyh   Y n0 tdƒ}tt	 	¡ ƒ}zt| 
¡  ¡ ƒ}W n ty¨   d	}Y n0 || d
k rºdS z$tdd|› d�ƒ | t|ƒ¡ W n tyð   Y n0 dS )zßAppend a timestamped error to the auto-trade error log and email it.
    Throttled: max 1 email per 30 minutes to prevent flooding inbox if the
    same failure mode hits many signals in a row (e.g. Kalshi API outage).
    ú[Úseconds©Ztimespecz] Ú
r   Nz$E:\soccer\.auto_trade_err_last_emailr   i  z[AUTO-TRADE ERROR]z<pre>z</pre>)r   rM   r  r   ÚAUTO_TRADE_ERR_LOGÚwriter»   r   rÊ   r,  Ú	read_textr   r¨  Z
write_textre   )r§  r   r   Zthrottle_fileZnow_tsZlast_tsr   r   r   Ú_auto_log_errorS  s&    ,
rÆ  c                     s.   t ƒ } t tj¡ d¡‰ t‡ fdd„| D ƒƒS )z<Sum $ committed (count*price) for orders placed today (UTC).r   c                 3   s,   | ]$}|  d d¡ ˆ ¡r|  dd¡V  qdS )Ú	placed_atr   Údollar_committedr   N)r:   r   r’  ©r  r   r   r‡   r  s   ÿz$_today_auto_spent.<locals>.<genexpr>)r½  r   rM   r   rb   rN   r%  )Úlogr   rÉ  r   Ú_today_auto_spentn  s    rË  c                    s   t ‡ fdd„tƒ D ƒƒS )a_  True if auto_trade_log.json already has an order for this fixture+side.
    Local idempotency guard: Kalshi only dedupes client_order_id against
    *resting* orders, so once the first order fills it will accept a duplicate
    (caused the 2026-05-29 Nice/STE double-bet). We never re-place on the same
    fixture+side regardless of fill status.
    c                 3   s   | ]}|  d ¡ˆ kV  qdS )r�  Nr¨   r’  ©r�  r   r   r‡   }  r,   z"_already_placed.<locals>.<genexpr>)r¿   r½  rÌ  r   rÌ  r   Ú_already_placedv  s    rÍ  c                    sd  z,ddl }ddlm}m}m} ddlm} W n2 ty^ } zdd|› �fW  Y d}~S d}~0 0 | |  	d¡ppd 
¡ ¡}|s�dd|  	d¡› �fS || d	 ƒ}|| d
 ƒ}	|r°|	sÚdd| d	 › d|› d| d
 › d|	› d�	fS |  	dd¡}
|
 
¡ }| d¡� rþdS d|v }|�r|	n|}z| | d ¡}W n@ t�yf } z&dd|  	d¡› d|› �fW  Y d}~S d}~0 0 ddl‰ddl‰d‰ ‡ ‡‡fdd„}d}|||||	ƒ||||	|ƒfD ]<}|› d|› �}||ƒ\}}|�rÜ||f  S |�r¬|}�q¬||||	ƒ}|�r|› d|› �|fS |�r@dd|› d|› d|	› d| d › d|› d�fS dd|› d|› d|	› d| d › �fS )zÞ
    Resolve the full Kalshi market ticker (event-outcome) for a signal.
    Returns (ticker_str, outcome_abr) or (None, reason_str).
    Side YES on AWAY -> outcome = away_abr; Side YES on HOME -> outcome = home_abr.
    r   N)r¼   Úfind_event_by_abrsr¹   rT  zimport: r}   r   zno Kalshi series for div=rš   r›   zmissing abr home=ú(z) away=r�  rÂ   rr   ZNO)NÚ__SKIP_NO__r�  rœ   z	bad date rµ   z-https://api.elections.kalshi.com/trade-api/v2c                    s®   d}t dƒD ]˜}zˆjˆ › d| › �dd�}W n8 tyd } z t|ƒj› d|› �}W Y d}~n6d}~0 0 |jdkrv dS |jd	kr† d
S d|j› �}ˆ d|d  ¡ qd|fS )zA(True, None) found | (False, None) absent | (False, err) unknown.Nr'   z	/markets/r�   )r¢   rµ   r£   )TNi”  )FNzHTTP g      ø?r   F)r*  r:   r»   ÚtypeÚ__name__r¾   r-  )ÚtkrZlastr.  rÅ   rÇ   ©ZBASEZ_timer½   r   r   Ú_probe¶  s    *

z*_resolve_ticker_for_signal.<locals>._prober¯   z0kalshi lookup FAILED (NOT a missing market) for r(  z% -- signal skipped, retry may succeedz#could not resolve event ticker for )r(   r¶   r¼   rÎ  r¹   r   rœ   r»   r¸   r:   r·   r   rº   r½   r,  )r‘   r(   r¼   rÎ  r¹   rW  rÇ   rÃ   Zhome_abrZaway_abrZside_rawZ
side_upperZis_awayZoutcomerÄ   rÕ  ZindeterminateZorderingrÓ  ÚfoundÚerrZevr   rÔ  r   Ú_resolve_ticker_for_signal€  sj    $*0ÿÿÿÿÿþ
rØ  c                 C   s¬   |   d¡}zt|ƒ}W n: ttfyP   td|   d¡› d|   d¡› d�ƒ Y dS 0 |dkr^dS |tk r¨td	|d
›dtd›d|   d¡› d|   d¡› d|   d¡› d�ƒ dS dS )as  Shared execution-only longshot filter (see AUTO_TRADE_MIN_PIN_FAIR).

    Called by BOTH real-order paths -- this module's Kalshi placer and
    poly_us_trader's -- so a signal cannot slip money onto one venue after being
    blocked on the other. Returns True when the bet is allowed to trade real.

    A missing/unparseable pin_fair ALLOWS the order rather than blocking it: xlow
    entries always carry the field (live_monitor sets it when building the entry),
    so absence means a non-xlow caller, and silently blocking every such signal
    would be a much larger behaviour change than the one that was asked for.
    Úpin_fairz   [AUTO] no pin_fair on signal (rš   r  r›   z ) -> longshot filter not appliedTr   z  [AUTO] pin_fair r—  z < rŒ  r  r(  rÂ   z!) -> paper only (longshot filter)F)r:   r?   r÷   rö   rÁ   ÚAUTO_TRADE_MIN_PIN_FAIR)ÚsignalrË   Zpfr   r   r   Úpin_fair_okÝ  s(    
ÿ
ÿÿÿ
rÜ  c                  C   s’  t sdS tj t¡r tdƒ dS tdur:|  d¡tvr:dS |  d¡tv rbtd|  d¡› d�ƒ dS |  d¡dvršt	sštd|  d	¡› d
|  d¡› d�ƒ dS t
| ƒs¦dS |  d¡rº|  d¡s¾dS d|  dd¡ ¡ v rÖdnd	}d|  d¡› d|› �}t|ƒ�rtd|› d�ƒ dS tƒ }tdu�rD|tk�rDtdt› d|d›d�ƒ dS t| ƒ\}}|�sÄ|dk�p’t|  dd¡ƒ d¡�o’t|  dd¡ƒdd… dk}|�sÀtd |  d	¡› d
|  d¡› d!|› �ƒ dS z>d"dl}| |¡ d#i ¡}t| d$¡�pðd"ƒ}	| d%d&¡}
W n< t�y> } z"td'|› d!|› �ƒ W Y d}~dS d}~0 0 |
d(k�rdtd|› d)|
› d*�ƒ dS |	d"k�sx|	d+k�r’td|› d,|	› d-�ƒ dS zPd"dl}d"dl}|j d"|j |j |j |j t¡¡¡d.¡¡ d"d/lm} W n t�yú   d0}Y n0 zd"d1lm} W n t�y$   d2}Y n0 |	|k �rTtd|› d3|	d4›d5|d›d6�ƒ dS d}z8t|  d7¡�pjd"ƒ}d"|  k �r†d8k �rŽn n|}W n t t!f�y¨   Y n0 |du �r*z\t|  d9¡�pÆd"ƒ}t|  d:¡�pÚd"ƒd; }|d<|  }d"|  k �rd8k �rn n|}W n t t!f�y(   Y n0 |du�rŒt"|	| ƒ| d=k�rŒtd>|› d?|d4›d@|	d4›�ƒ td|› dA|d4›d@|	d4›d*�ƒ dS t| d ƒ}t"|	| ƒ| d=k�rÌtd>|› dB|› dC|	› �ƒ dS t| d ƒt#g}tdu�rô| $t| ¡ t%|ƒ}t&t'|	dD ƒƒ}|d8k �rdS |d; }t&t( )t'|| dEƒ¡ƒ}|d8k �r`tdF|d›dG|› dH�ƒ dS t%||j*ƒ}|| d; }| +dI¡�rˆdS z&|j,|dJ|||t-dK�}| dL|¡}W nH t�yö } z.tdM|› dN|› dO|› d!|› �ƒ W Y d}~dS d}~0 0 t. /t0j1¡j2dPdQ�| dR¡||dJ||||  d¡|  d	¡|  d¡|  d¡||	dSœ}t3ƒ }| $|¡ t4|ƒ tdT|› dN|› dO|› dU|d›dV| dR¡› �
ƒ |S )Wa€  
    Auto-place a Kalshi limit-at-ask order for an eligible CLV signal.
    Returns dict with order details on success, None on skip/failure.

    Safety gates (in order):
      1. AUTO_TRADE_ENABLED master flag
      2. Kill-switch file exists -> abort
      3. League whitelist
      4. Signal has kalshi_stake / kalshi_ask
      5. Daily $ cap not exceeded
      6. Ticker resolves on Kalshi
      7. Re-fetch live ask; reject if drifted >20% from signal ask
      8. Sanity: ask < 0.95 (can't price above 95c)
      9. Idempotent client_order_id (Kalshi rejects duplicates)
      10. kalshi_trader internal caps re-enforce $1/order
    Nz$  [AUTO] kill-switch engaged -> skipr}   z	  [AUTO] z( excluded from real orders -> paper onlyr‰  )ZxlowZxlow_polyusz  [AUTO] baseline signal (rš   r  r›   z3) -> paper only (BASELINE_AUTO_TRADE_ENABLED=False)r  r  r�  rÂ   rr   zauto-rå   r¯   z$ already in log -> skip (idempotent)z  [AUTO] daily cap $z reached (spent=$rŒ  z	) -> skiprÐ  rœ   r   Z20r'   Z2099zticker resolve failed for rµ   r   ZmarketZyes_ask_dollarsr¦   r™  zget_market failed for Zactivez status=z -> skipgffffffî?z
 live_ask=z out of range -> skipZresearch)ÚXLOW_PRICE_FLOORgš™™™™™¹?)ÚBAND)r    r!   z ask=r—  z below the f10 floor z -> paper onlyZentry_k_midr   rÙ  Z
clv_kalshig      Y@r%   gš™™™™™É?zprice drift z	: signal z	 -> live z drifted: signal z	: signal=z live=rÏ   é	   z  [AUTO] budget=$z at zc -> no sizezxlow place_auto_orderr«   )r�  Z
subaccountÚorderzplace_limit_order failed r(  rš  rÀ  rÁ  r“  )rÇ  r“  r�  r®   rZ  r”  r•  rÈ  rå   rš   r›   r}   Ú
signal_askÚlive_askz  [AUTO] PLACED zc ($z) id=)5rž  rï   r   rñ   ÚAUTO_TRADE_KILL_FILErÁ   rŸ  r:   r�  ÚBASELINE_AUTO_TRADE_ENABLEDrÜ  r·   rÍ  rË  ÚAUTO_TRADE_DAILY_CAPrØ  re   r   rÆ  Zkalshi_traderZ
get_marketr?   r»   ÚsysÚinsertr�   ÚdirnameÚabspathÚ__file__Zforward_test_unique_bookrÝ  rÞ  r÷   rö   ÚabsÚAUTO_TRADE_PER_BET_CAPrÚ   rr  rÊ   rL   ÚmathZceilZMAX_CONTRACTS_PER_ORDERZkalshi_floor_breachedZplace_limit_orderÚXLOW_SUBACCOUNTr   rM   r   rb   r  r½  r¾  ) rÛ  Zbet_normr�  Zspentr®   rQ  Zis_skipZktZmkrâ  r¦   rÇ   Ú_sysÚ_osZ_F10Z_BANDZ_refÚ_mZ_f2Z_c2Z_p2rá  ZcapsZbudgetr•  Z_pxr”  rÈ  Zresprà  ÚrecordrÊ  r   r   r   Úplace_auto_orderú  s   "

þ&

ÿÿ


ÿ

  


þ ò
.ró  c                  C   s\   zt  ttƒjdd�¡} W n ty2   tƒ  Y S 0 t| tƒrB| n
|  	d| ¡}dd„ |D ƒS )að  (date|home|away) for every bet already RESOLVED into bet_history.

    Dedupe used to consult pending_bets.json alone, which only protects a bet
    while it is still open. An early-kickoff match that SETTLED between two runs
    of this job left pending, and the next run happily logged the same signal a
    second time -- booking its P&L twice. Found 2026-08-08 on Independiente
    Rivadavia v Estudiantes de Rio Cuarto (Liga Profesional, -1.67 counted
    twice). Rare today because most fixtures resolve after the day's last run,
    but Liga Profesional, Liga MX and the Asian leagues all kick off inside the
    working day, so it gets more likely as coverage grows.

    Keyed WITH the date so a genuine rematch on another day still logs.
    r   rj   r[   c              	   S   s>   h | ]6}t | d ¡ƒdd… › d| d¡› d| d¡› �’qS )rœ   Nr�   r@  rš   r›   )re   r:   r#  r   r   r   rŒ   ó  s   ÿz _settled_keys.<locals>.<setcomp>)
r@   rÙ   r   r]   rÅ  r»   rŽ   r×   rØ   r:   )rË   rú   r   r   r   Ú_settled_keysà  s    ÿrô  c           
      C   s   t s | rtdt| ƒ› d�ƒ d S tƒ }dd„ |D ƒ}tƒ }| D �]²}|d › d|d › �}t| d¡ƒd d	… › d|d › d|d › �|v r¬td
|d › d|d › d�ƒ q>||vr>|d |d |d |d |d | dd¡| d¡|d |d |d |d dœ}| d¡�r&|d |d< |d |d< | d¡�rJ|d |d< |d |d< | |¡ | |¡ t	r>zBt
|ƒ}|�r¢dD ]*}|| |d|› �< || |d|› �< �qvW q> t�yð }	 z2td| d¡› d| d¡› d|	› �ƒ W Y d }	~	q>d }	~	0 0 q>t|ƒ d S )Nz  [b365 RETIRED] z baseline signals NOT stakedc                 S   s"   h | ]}|d  › d|d › �’qS ©rš   r@  r›   r   r#  r   r   r   rŒ      r,   z!add_to_pending.<locals>.<setcomp>rš   r@  r›   rœ   r�   r_  r  z( already settled today -- not re-loggingr/  r}   rÂ   rr   rå   r  r  rc  rb  )rœ   rš   r›   r/  r}   rÂ   rå   r  r  rc  rb  r  r  r  r  )r“  rÇ  r”  r•  Zauto_z add_to_pending hook crashed for rµ   )ÚB365_BETTING_ENABLEDrÁ   rˆ   rY   rô  re   r:   rÚ   rG  rž  ró  r»   rÆ  r\   )
rw  r  Zexisting_keysr§   r‘   r   ÚentryZrecr   rÇ   r   r   r   Úadd_to_pending÷  sJ    
2û

>rø  c                    s¨   ddl }g d¢}tj t¡ }ttddd��h}|j||dg d�}|rN| ¡  | D ]2‰ ‡ fd	d
„|D ƒ}t 	¡  
d¡|d< | |¡ qRW d  ƒ n1 sš0    Y  dS )z'Append signals to CSV log for tracking.r   N)rœ   r,  r/  r}   rš   r›   r  ra  rb  rc  rd  r  re  rf  rg  r  rh  ri  r  rj  r  rk  rl  r  r   r   )ÚnewlineZ	logged_at)Z
fieldnamesc                    s   i | ]}|ˆ   |d ¡“qS )r   r¨   r¶  r®  r   r   r+   1  r,   zlog_signals.<locals>.<dictcomp>rG   )Úcsvrï   r   rñ   Ú
SIGNAL_LOGr   Z
DictWriterZwriteheaderr   rM   rN   Zwriterow)rw  rú  ÚfieldsZwrite_headerr   rE   rû   r   r®  r   Úlog_signals$  s    rý  c            !         s~  t  ¡  d¡‰t  ¡  d¡} tdˆ› d| › d�ƒ tdƒ tƒ }tƒ }tƒ }t  ¡ tdd�  d¡}d	d
„ |D ƒ}tƒ D ]F}| 	dd¡|krx|d › d|d › �}||vrx| 
|¡ | |¡ qxtƒ }tdd„ |D ƒƒ}	t||	 dƒ}
tdt|ƒ› d|d›d|d›�ƒ |�r0tdt|ƒ› d|	d›d|
d›�ƒ tdƒ t||ƒ\}}tdt|ƒ› �ƒ |D ]V}td|d › d|d › d|d › d |d! › d"|d# › d$|d% › d&|d' d(›�ƒ �q\‡fd)d*„|D ƒ}|D ]l‰ t‡ fd+d„|D ƒƒ�sÊd,ˆ d!  }| 
i ˆ ¥ˆ  	d-d¡td,| d.ƒ|tˆ  	d/d¡d0 d,ƒd1d2œ¥¡ �qÊt|ˆƒ\}}}td3t|ƒ› �ƒ d4d*„ |D ƒ}|�rvt|ƒ |�s²|�r†d5nd6}d7|d›�}tg ˆ|||�p¨d |d8�}nþt|ƒ}td9d„ |D ƒƒ}td:d„ |D ƒƒ}td;d„ |D ƒƒ}td<d„ |D ƒƒ}g }|�r| 
|› d=�¡ |�r2| 
|› d>�¡ |�rR|�rRd?|d›d@|d›�n|�rddA|d›�n
dB|d›�}dC |¡› dD|d,k�rŠdEnd› dF|› �}t|ˆ|||�pªd dG�}|�rætdHd„ |D ƒƒ}dI|› dJt|ƒ| › dK|› �}tdL|› �ƒ d1}dMd*„ |D ƒ} | �rD|�rDt| ˆ||ƒ t| ƒ t| ƒ tdNt|ƒ› dO�ƒ |�rrtdPt|ƒ› dQtdRd„ |D ƒƒd›�ƒ tdSƒ d S )TNr   z%Az
=== Away CLV Signal Scanner - r  z) ===zChecking pending bets...é   )Zhoursc                 S   s"   h | ]}|d  › d|d › �’qS rõ  r   r=  r   r   r   rŒ   G  r,   zmain.<locals>.<setcomp>r  r   rš   r@  r›   c                 s   s   | ]}|  d d¡V  qdS r"  r¨   r#  r   r   r   r‡   O  r,   zmain.<locals>.<genexpr>r   r  z  |  Bankroll: $r
  z  |  Kalshi Bankroll: $z  Pending: z bets ($z committed)  |  Available: $z*Fetching today's odds from API-Football...z  Signals found: z    r/  rµ   r  z  B365A=r  z  fair=ra  z  edge=rb  z
%  stake=$r  rŒ  c                    s(   g | ] }|  d ¡ˆ kr|  d¡r|‘qS )rœ   r  r¨   r#  rÉ  r   r   r­   ^  r,   zmain.<locals>.<listcomp>c                 3   s.   | ]&}|d  ˆ d  ko$|d ˆ d kV  qdS )rš   r›   Nr   rŽ  )r$  r   r   r‡   `  r,   r   r,  rF   rc  rÏ   T)r,  ra  re  rd  r°  z  New (not yet sent): c                 S   s   g | ]}|  d ¡s|‘qS ©r°  r¨   rŽ  r   r   r   r­   r  r,   zAll signals already sent today.z&No Away bets met the 5% CLV threshold.zNO NEW SIGNALS - $)r  r  r   c                 s   s   | ]}|  d ¡pdV  qdS r"  r¨   rŽ  r   r   r   r‡   ~  r,   c                 s   s   | ]}|  d ¡pdV  qdS r\  r¨   rŽ  r   r   r   r‡     r,   c                 s   s    | ]}|  d ¡dkrdV  qdS )rÂ   rr   r   Nr¨   rŽ  r   r   r   r‡   €  r,   c                 s   s    | ]}|  d ¡dkrdV  qdS )rÂ   rq   r   Nr¨   rŽ  r   r   r   r‡   �  r,   z Awayz HomezB365 $z + Kalshi $zKalshi $r–  z + z CLV BETÚSr›  )r  r  c                 s   s   | ]}|d  rdV  qdS rz  r   r=  r   r   r   r‡   Œ  r,   r¿  zW/zL] z  Subject (suppressed): c                 S   s   g | ]}|  d ¡s|‘qS rÿ  r¨   rŽ  r   r   r   r­   –  r,   z  z$ signals logged and added to pendingz
  SUMMARY: z signals, total stake $c                 s   s   | ]}|  d d¡V  qdS r"  r¨   rŽ  r   r   r   r‡   ž  r,   zDone.)r   rM   rN   rÁ   r  rC   rR   r   r^   r:   rÚ   rG  rY   r%  rm  rˆ   ry  r¿   rL   rµ  rø  r¢  r�   r¼  rý  )!Zday_namer  ZBANKROLLZKALSHI_BANKROLLZrecent_cutoffZresolved_idsr  r   r  r�  rv  rw  Zhas_oddsr‘   Zpending_todayr?  Znew_signalsr²  r³  Z	fresh_prer   r¦  r£  ÚnZ
total_b365Ztotal_kalshiZn_awayZn_homerÌ   Z	stake_strr‚  Zsent_okZfreshr   )r$  r  r   Úmain:  s¢    

"",ÿÿÿÿ
ú	 ÿ((r  Ú__main__)r¥   )NNN){Ú__doc__Zpandasry   Zurllib.requestrx   r½   r¥  rí  r@   ræ  rï   r,  Úpathlibr   Zemail.mime.multipartr   Zemail.mime.textr   r   r   r   r(   ÚstdoutÚstderrr¡  Úreconfigurer»   r   Z_envr:   r¤  r   r   rs  rö  rn  rp  ro  rq  rt  rð   r>   rQ   rT   rX   r]   r©  rû  rž  rî  rŸ  r�  rå  rì  rÚ  rä  rœ  rÃ  rã  ZAF_KEYr)  r+  rC  rE  rD  Zall_divsrB  ru  r9   r8   rC   rP   rR   rS   rU   rV   rY   r\   r^   r`   rh   rw   rƒ   r‰   r’   r˜   rÈ   rÎ   rÝ   rè   r  ré   rò   rü   r  r!  r&  rä   rR  rX  r[  ry  r‡  r¢  r¨  rª  r­  r¯  rµ  r¼  r½  r¾  rÆ  rË  rÍ  rØ  rÜ  ró  rô  rø  rý  r  rÒ  r   r   r   r   Ú<module>   sº  	[2Žu	73#?û& K
 s#
 H
] g-h
