a
    ‡tIj-  ã                   @   sž  d Z ddlZddlZddlZddlZdZe› d�Ze› d�Ze› d�Zddd	d
ddœZ	dZ
i Zd;dd„Zeƒ Ze dd¡Ze dd¡ZdddœZdadd„ Zd<dd„ZzddlmZmZ W n" eyÜ   dd„ Zdd„ ZY n0 d=d d!„Zd>d"d#„Zd$d%„ Zd&d'„ Zd(d)„ Zd?d*d+„Zd,d-„ Zd@d.d/„Z e!d0k�ršddl"Z"e"j#j$d1d2d3� e�she%d4ƒ e%d5ƒ e" &d¡ e%d6eƒ �rxd7nd8ƒ e%d9ƒ eƒ  e%d:ƒ e ƒ  dS )Au`  
Novig live price checker (read-only) â€” soccer match-winner markets. SKELETON.

Third exchange leg alongside kalshi_live.py / polymarket_live.py. Exposes the
same {home,draw,away} x {bid,ask,mid} dict shape from get_all_prices() so the
rest of the pipeline treats all three books uniformly.

STATUS (2026-07-04): written against docs.novig.com before credentials exist.
Auth flow, events and open-markets endpoints are per the official docs; the
ORDER-BOOK response shape is UNVERIFIED (docs index lists "Get order book" but
the schema page wasn't public) â€” verify + fix _best_bid_ask() on first run
with real credentials. Everything degrades to None without creds, so importing
this module never breaks the monitor.

Auth: OAuth2 client-credentials -> 30-min bearer tokens (re-request on expiry).
Put in E:\soccer\.env:
    NOVIG_CLIENT_ID=...
    NOVIG_CLIENT_SECRET=...
Credentials come from Novig directly (account holder API access).

READ-ONLY BY DESIGN: no order-placement endpoints in this module. If Novig
execution is ever wanted it gets its own novig_trader.py with hard caps,
mirroring kalshi_trader.py.
é    Nzhttps://api.novig.usz/nbx/v1/auth/emm-tokenz/nbx/v2/emm/eventsz/nbx/v2/emm/markets/openZEPLÚMLSZLALIGAZSERIEAZ
BUNDESLIGA)ZE0r   ZSP1ZI1ZD1é<   úE:\soccer\.envc                 C   sl   i }zPt | ƒD ]B}| ¡ }|r| d¡sd|v r| dd¡\}}| ¡ || ¡ < qW n tyf   Y n0 |S )Nú#ú=é   )ÚopenÚstripÚ
startswithÚsplitÚ	Exception)ÚpathÚenvÚlineÚkÚv© r   úE:\soccer\main\novig_live.pyÚ	_load_env1   s    r   ZNOVIG_CLIENT_IDÚ ZNOVIG_CLIENT_SECRETç        )ÚvalueÚexpiresFc               
   C   sÜ   t rtststdƒ dadS td r<t ¡ td k r<td S zftjtdt tdœdd	�} |  	¡  |  
¡ }| d
¡td< t ¡ tt| dd¡ƒdƒ d td< td W S  tyÖ } ztd|› �ƒ W Y d}~dS d}~0 0 dS )zABearer token via client-credentials; cached, renewed 5 min early.uF     [novig] NOVIG_CLIENT_ID/SECRET not in .env â€” novig prices disabledTNr   r   Zclient_credentials)Z
grant_typeZ	client_idZclient_secreté   )ÚdataÚtimeoutZaccess_tokenZ
expires_ini  i,  z  [novig] auth failed: )Ú	CLIENT_IDÚCLIENT_SECRETÚ_warned_no_credsÚprintÚ_tokenÚtimeÚrequestsZpostÚ	TOKEN_URLZraise_for_statusÚjsonÚgetÚminÚintr   )ÚrÚjÚer   r   r   Ú
_get_tokenD   s.    ýü&
r+   c                 C   s^   t ƒ }|sd S z6tj| dd|› �i|dd�}|jdkr<W d S | ¡ W S  tyX   Y d S 0 d S )NZAuthorizationzBearer r   )ZheadersÚparamsr   éÈ   )r+   r"   r%   Zstatus_coder$   r   )Zurlr,   Útokr(   r   r   r   Ú_get^   s    ÿ

r/   )Ú_team_matchÚ_normc                 C   s:   dd l }| d| pd¡ dd¡ ¡  ¡ } t dd| ¡ ¡ S )Nr   ZNFKDr   ÚasciiÚignorez
[^a-z0-9 ]ú )ÚunicodedataZ	normalizeÚencodeÚdecodeÚlowerÚreÚsubr	   )Úsr5   r   r   r   r1   p   s     r1   c                 C   s@   t | ƒt |ƒ }}||kp>||v s*||v o>tt|ƒt|ƒƒdkS )Né   )r1   r&   Úlen)ÚnameZteamZnaZnbr   r   r   r0   t   s    r0   c              	   C   s”   t   ¡ }t | ¡}|s6|dur6||d  tk r6|d S g }dD ]B}tt| |ddœd�}|r>|t|tƒrj|n| d| dg ¡¡7 }q>||d	œt| < |S )
zJOpen pregame + ingame events for a Novig league string, cached _CACHE_TTL.NÚtsÚevents)ZOPEN_PREGAMEZOPEN_INGAMEéd   )ÚleagueÚstatusÚlimit©r,   r   )r?   r@   )r!   Ú_cacher%   Ú
_CACHE_TTLr/   Ú
EVENTS_URLÚ
isinstanceÚlist)Znovig_leagueÚforceZnowÚcr@   rC   r)   r   r   r   Ú_fetch_eventsz   s    
(rM   c                 C   s
  t | dƒr|  ¡ } t| ƒdd… } |r0t |¡nd}|r>|gndd„ t ¡ D ƒ}|D ]°}t|ƒD ]¢}| d¡ppi }| d¡p~i  dd	¡}	| d
¡p”i  dd	¡}
|	r`|
s¨q`t|	|ƒr¼t|
|ƒsÒt|	|ƒr`t|
|ƒsÒq`t| dd	¡ƒdd… }|rø|| krøq`|    S qTdS )u  Novig event dict for this fixture, or None.
       game_date: 'YYYY-MM-DD' string or datetime.date (normalized here â€” see the
       polymarket_live date-object lesson, 2026-07-03).
       league: our div code (e.g. 'E0') -> Novig league string via NOVIG_LEAGUE.Ú	isoformatNé
   c                 S   s   g | ]}|r|‘qS r   r   ©Ú.0r   r   r   r   Ú
<listcomp>’   ó    zfind_event.<locals>.<listcomp>ÚgameÚhomeTeamr>   r   ÚawayTeamÚscheduledStart)ÚhasattrrN   ÚstrÚNOVIG_LEAGUEr%   ÚvaluesrM   r0   )Ú	game_dateÚ	home_teamÚ	away_teamrB   ÚnlÚleaguesÚlgr*   ÚgÚhÚaÚstartr   r   r   Ú
find_event‰   s.    
ÿÿrf   c                 C   sž   |   d¡}|r.|D ]}|  d¡dkr|  S qt|   d¡p<g ƒ}|sHdS tt|   d¡ddœd�}t|tƒrn|n|pti   dg ¡D ]}|  d	¡|v r~|  S q~dS )
u"  The match-winner (MONEY) market dict for an event, or None.
    Markets ride on the event via marketIds; fetch open markets for the league
    and join. TODO(creds): confirm whether markets are embedded on the event
    (EventWithMarketsDto) â€” if so, read them directly and skip the join.ZmarketsÚtypeZMONEYZ	marketIdsNrB   )rB   Z
marketTyperE   Úid)r%   Úsetr/   Ú	OPEN_MKTSrI   rJ   )ZeventZmktsÚmZidsr)   r   r   r   Ú_money_market¤   s    

"
rl   c           	      C   sÔ   t t› d| › d�ƒ}|sdS z | d|¡}t|tƒr8|n
| dg ¡D ]t}| d| d¡¡|kr`qD| d¡plg }| d	¡pzg }|r�t|d
 d ƒnd}|r¨t|d
 d ƒnd}||f  W S W n tyÎ   Y n0 dS )uv  (bid, ask) for one outcome from the order book.
    TODO(creds): UNVERIFIED â€” the docs index lists a 'Get order book' endpoint but
    the path/schema weren't public. Likely GET /nbx/v2/emm/markets/{id}/orderbook.
    Adjust path + parsing on first authenticated run; until then returns (None, None)
    and get_all_prices falls back to last-trade for mid with no bid/ask.z/nbx/v2/emm/markets/z
/orderbook)NNÚoutcomesZ	orderbookZ	outcomeIdrh   ÚbidsÚasksr   ZpriceN)r/   ÚBASEr%   rI   rJ   Úfloatr   )	Z	market_idZ
outcome_idr)   ZbookÚorn   ro   ÚbidÚaskr   r   r   Ú_best_bid_ask¸   s     ru   c                 C   s�   |   dd¡pd}t|   d¡tƒr6|   d¡p,i   dd¡nd}d| ¡ v sRd| ¡ v rVdS ||fD ],}|rvt||ƒrv dS |r^t||ƒr^ dS q^d	S )
z<'home' / 'draw' / 'away' for an OutcomeResponseDto, or None.Zdescriptionr   Z
competitorr>   ZdrawZtieÚhomeZawayN)r%   rI   Údictr8   r0   )rr   r]   r^   ZdescZcompZcandr   r   r   Ú_classify_outcomeÐ   s    *rx   c              
   C   s,  t | |||d�}|sdS t|ƒ}|s(dS i }| d¡p8g D ]æ}t|||ƒ}|sPq:t| d¡| d¡ƒ\}	}
| d¡}z|dur†t|ƒnd}W n ttfy¦   d}Y n0 |	r¼|
r¼|	|
 d n|
pÂ|}|du rÎq:|	rÜt|	dƒnd||› d	�< |
røt|
dƒnt|dƒ||› d
�< t|dƒ||› d�< q:|�p*dS )a/  Fetch home/draw/away prices from Novig. Returns dict or None.
       Shape matches kalshi_live/polymarket_live:
         {home_bid,home_ask,home_mid, draw_*, away_*}
       Prices are probabilities in [0,1] (Novig quotes 0.55-style).
       mid = (bid+ask)/2 when bid present, else ask, else last trade.)rB   Nrm   rh   Úlasté   é   r   Z_bidZ_askZ_mid)	rf   rl   r%   rx   ru   rq   Ú	TypeErrorÚ
ValueErrorÚround)r\   r]   r^   rB   Zevrk   Úresultrr   Zlabelrs   rt   ry   Zmidr   r   r   Úget_all_pricesÞ   s0    

"r€   c                  C   s   t t› d�ƒ} t| ƒ | S )uJ   All league identifiers Novig currently lists â€” use to fill NOVIG_LEAGUE.z/nbx/v2/emm/events/leagues)r/   rp   r   )r)   r   r   r   Úlist_leaguesÿ   s    r�   c                 C   sÞ   | rt  | ¡rt |  gndd„ t  ¡ D ƒ}|D ]ª}t|dd�}td|› dt|ƒ› d�ƒ |D ]z}| d¡pli }| d	¡pzi  d
d¡}| d¡p�i  d
d¡}tdt| dd¡ƒdd… › d|› d|› d| d¡› d�	ƒ q\q.dS )z7Print open events for a league (or all mapped leagues).c                 S   s   g | ]}|r|‘qS r   r   rP   r   r   r   rR     rS   z list_matches.<locals>.<listcomp>T)rK   ú[z] z open eventsrT   rU   r>   ú?rV   z   rW   r   Né   z  z vs z  [rC   ú])rZ   r%   r[   rM   r   r=   rY   )rB   r`   ra   Zevsr*   rb   rc   rd   r   r   r   Úlist_matches  s    ÿr†   Ú__main__zutf-8Úreplace)ÚencodingÚerrorsu9   No NOVIG_CLIENT_ID in .env â€” add credentials, then run:z@  python main\novig_live.py           # auth check + open eventszauth:ZOKZFAILEDz
leagues on Novig:z
open events in mapped leagues:)r   )N)F)N)N)N)'Ú__doc__Úosr!   r9   r"   rp   r#   rH   rj   rZ   rG   rF   r   Z_envr%   r   r   r    r   r+   r/   Zpolymarket_liver0   r1   ÚImportErrorrM   rf   rl   ru   rx   r€   r�   r†   Ú__name__ÚsysÚstdoutÚreconfigurer   Úexitr   r   r   r   Ú<module>   s^   


û	





!


