a
    ¸Ï”j�  ã                   @   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	m
Z
 ej dd¡ ddlZddlZddlZddlZddlZe
dƒZed Zed d Zed d	 Zd
ZdZdZdadZdZ dZ!dZ"g d¢Z#da$dd„ Z%e ej&e%¡ dd„ Z'dZ(e )d¡Z*d/dd„Z+dd„ Z,dd„ Z-dd„ Z.d Z/d!Z0d"d#„ Z1d$d%„ Z2e3ƒ ddfd&d'„Z4d(d)„ Z5d*d+„ Z6d,d-„ Z7e8d.k�rxe 9e7ƒ ¡ dS )0a?  Live three-leg maker for Poly-US. Minimal first run.

SEPARATE PROCESS FROM mm_maker_live.py ON PURPOSE. That one holds real Kalshi
positions and is completing at 76.7%; nothing here may be able to break it.

WHAT IS PROVEN AND WHAT IS NOT (2026-08-22):
  PROVEN live, at $0.00 cost -- post-only refuses crosses; a 90s GTD expires the
  order without us; cancel works and verifies; three legs of one book rest at
  once; the reserved $100 is tradeable; BUY_SHORT prices YES-side.
  NOT PROVEN -- nothing has ever FILLED here. Fill detection, the maker rebate
  landing, and partial-fill handling are all untested. This run exists to
  exercise exactly those. Treat its output as an experiment, not a P&L.

DELIBERATELY SMALLER THAN THE KALSHI MAKER. Poly-US books are 7-20 deep against
Kalshi's ~3,400, so size and gates do not transfer. Caps live in poly_us_maker
(5 contracts/leg, $15 resting, 15 orders) and are not raised here.

A 200 IS NOT A RESTING ORDER. A post-only kill returns HTTP 200 with an order
id. Every post is confirmed against open_orders() before it is treated as live;
that mistake already cost an entire debugging cycle today.

Usage:
    python main\mm_maker_live_polyus.py --minutes 60            dry
    python main\mm_maker_live_polyus.py --live --minutes 60     ARMED
    create E:\soccer\STOP_PUS_MAKER to stop it
é    N)ÚPathzE:\soccer\mainz	E:\soccerZSTOP_PUS_MAKERZresearchzpus_live_state.jsonzpus_live_legs.csvg      8@é-   ç      ð?Fé   g       @g{®Gázt?g¸…ëQ¸ž?©ZtsÚeventÚslugÚsideZ	yes_priceZ	touch_askZ	touch_bidZabove_touch_cÚgross_cÚ	collectedÚneedÚrestedÚlivec                 C   s   da tdƒ d S )NTz+
[signal] stopping -- cancelling everything)Ú_stopÚprint)Z_sZ_f© r   úmain/mm_maker_live_polyus.pyÚ_sig_   s    r   c                    s˜   z€t jjddd� t  ¡  }t jdddd��B}tj|td�}|rH| ¡  | 	‡ fdd	„tD ƒ¡ W d   ƒ n1 st0    Y  W n t
y’   Y n0 d S )
NT©ÚparentsÚexist_okÚaÚ úutf-8)ÚnewlineÚencoding)Z
fieldnamesc                    s   i | ]}|ˆ   |d ¡“qS )r   ©Úget)Ú.0Úk©Úrowr   r   Ú
<dictcomp>p   ó    zlog_leg.<locals>.<dictcomp>)ÚLEG_LOGÚparentÚmkdirÚexistsÚopenÚcsvZ
DictWriterÚ
LEG_FIELDSZwriteheaderZwriterowÚOSError)r!   ÚnewZfhÚwr   r    r   Úlog_legh   s    
:r.   z(20\d\d)-(\d\d)-(\d\d)c              	   C   s´   |pt j ¡ }|t jtd� }g }| D ]n}t |¡}|s:q&z0t  t| d¡ƒt| d¡ƒt| d¡ƒ¡}W n t	y€   Y q&Y n0 ||k r&| 
|¡ q&|D ]}|  |d¡ qšt|ƒS )a¼  Drop legs whose match is more than STATE_KEEP_DAYS old.

    Nothing ever removed entries, so state grew without bound and -- worse --
    `held_events` kept reporting every book ever part-filled as still
    committed. Those events fall outside the window so nothing was quoted, but
    the set grew forever and every settled book stayed "committed" in a file
    that is read and rewritten every cycle.

    The match date is in the slug (atc-bra-int-cam-2026-08-22-int), so no
    lookup is needed. Entries with no parseable date are KEPT -- deleting
    something we cannot date is the more dangerous mistake, since a wrongly
    dropped fill makes the maker re-quote a leg it already holds.
    )Zdaysé   é   r   N)ÚdtÚdateÚtodayZ	timedeltaÚSTATE_KEEP_DAYSÚ_DATE_REÚsearchÚintÚgroupÚ
ValueErrorÚappendÚpopÚlen)Ústr3   ÚcutoffZdropr   ÚmÚdr   r   r   Úprune_statey   s     
0
rA   c                	   C   s4   zt  tjdd�¡W S  ttfy.   i  Y S 0 d S )Nr   ©r   )ÚjsonÚloadsÚSTATE_FÚ	read_textr+   r9   r   r   r   r   Ú
load_state™   s    rG   c                 C   sX   z@t jjddd� t  d¡}|jtj| dd�dd� | t ¡ W n tyR   Y n0 d S )NTr   z	.json.tmpr/   )Úindentr   rB   )	rE   r%   r&   Zwith_suffixZ
write_textrC   ÚdumpsÚreplacer+   )r=   Útmpr   r   r   Ú
save_state    s    
rL   c                 C   s²   |   d¡|   d¡ }}|r|s"dS ztj t|ƒ ¡ d¡ ¡ }W n tyT   Y dS 0 tj ||¡}z(ddl	m
} |j|dƒd� tjj¡W S  ty¬   | tjj¡ Y S 0 dS )	aÑ  Kickoff as an aware UTC datetime, or None.

    The watchlist stores `game_date` (a date) and `kickoff` ('HH:MM', ET) as
    separate fields; there is no shared helper for this. An earlier version of
    this file called mm_quoter_polyus._ko_of / live_monitor._ko_of, NEITHER OF
    WHICH EXISTS -- so every match silently resolved to None, no book ever
    entered the window, and the dry run reported a tidy "posted 0 gated 0" that
    looked like a quiet market rather than a broken function.

    live_monitor.clock_elapsed_min treats kickoff as naive LOCAL time because
    this box runs on ET. Made explicit here instead: America/New_York -> UTC, so
    it stays correct if the machine's timezone ever changes.
    Z	game_dateZkickoffNz%H:%Mr   )ÚZoneInfozAmerica/New_York)Ztzinfo)r   r1   ÚdatetimeÚstrptimeÚstrÚstripÚtimer9   ZcombineZzoneinforM   rJ   Z
astimezoneÚtimezoneÚutcÚ	Exception)r?   ÚgdZksÚtZnaiverM   r   r   r   Ú_ko_utcª   s    rX   é   g      @c                 C   sF   t ƒ }|  ¡ D ]2\}}t|tƒr| d¡r| | dd¡d ¡ q|S )zNEvent prefixes where at least one leg has filled -- books we are committed to.Úfill_pxú-r/   r   )ÚsetÚitemsÚ
isinstanceÚdictr   ÚaddÚrsplit)r=   Úoutr   Úvr   r   r   Úheld_eventsÍ   s
    rd   c                 C   s:   | pi   |¡pi }|  d¡s dS t|  d¡p.dƒtd kS )z'Is this leg filled all the way to SIZE?rZ   FÚfill_qtyr   ç•Ö&è.>©r   ÚfloatÚSIZE)r=   r   Úsvr   r   r   Ú_filled_to_sizeÖ   s    
rk   c              	      sš  t  ¡ \}}g }|D ]’}| d¡p$d ¡ }|tjvr6qt|ƒ}	|	du rHq|	|   ¡ d }
d|
  k ojtkn  }t	 |
  ko„dkn  }|s”|s”q| 
|
|||f¡ q|jdd„ d� g d }}|D �]Ì\}
}}}t|ƒtksè|tkrî �q–i }d	D ]8}t || d
¡| d¡|¡}|�s" �q0|d ||< qöt|ƒdksÆtt| ¡ ƒƒdk�rTqÆtt| ¡ ƒƒ dd¡d }||v }|�r†|�s†qÆ|�r”|�s”qÆ|d7 }i }d	D ]b}t || ¡}|�rÜ| d¡du �sÜ| d¡du �râ �q|| t|d ƒt|d ƒf||< �q¤t|ƒdk�rqÆ|�r<t‡ fdd„| ¡ D ƒƒ�r<qÆtdd„ | ¡ D ƒƒd d }|tk �rl|�slqÆ| 
| d
¡› d| d¡› �|||
f¡ qÆ|S )að  Up to MAX_BOOKS books that CLEAR THE GATE, nearest kickoff first.

    TWO THINGS THIS GETS RIGHT THAT THE FIRST VERSION DID NOT.

    1. GATE BEFORE TRUNCATING. It used to price every fixture in the window,
       sort nearest-kickoff-first, take 3, and only then apply the gross gate --
       inherited from the Kalshi maker, where fill rate rises steeply toward
       kickoff so nearest-first is right. On Poly-US the overround COMPRESSES
       into kickoff (measured 2026-08-22: +0.0 to +1.0c inside 1h, +2.0 to
       +3.0c at 3-4h), so nearest-first reliably picked the three tightest
       books, gated all three, and quoted nothing while seven qualifying books
       sat further out untouched.

    2. STOP PROBING ONCE FULL. Pricing every fixture in a 6h window is ~55
       fixtures x 3 legs = 165 requests per cycle, which is what got this
       account rate limited by Cloudflare. We probe in kickoff order and stop
       as soon as MAX_BOOKS qualify, with a hard MAX_PROBES ceiling.
    Zlcr   Ng      ¬@r   c                 S   s   | d S )Nr   r   )rW   r   r   r   Ú<lambda>	  r#   z!books_in_window.<locals>.<lambda>)Úkey)ÚhomeZdrawÚawayrn   ro   r   r   r[   r/   ZbestAskZbestBidc                 3   s   | ]}t ˆ |d  ƒV  qdS )r   N)rk   ©r   rc   ©Úst_refr   r   Ú	<genexpr>8  r#   z"books_in_window.<locals>.<genexpr>c                 s   s   | ]}|d  V  qdS )r0   Nr   rp   r   r   r   rs   :  r#   r   ç      Y@z vs )ÚLMZload_watchlist_filer   ÚupperÚpricesZLEAGUE_SLUGrX   Ztotal_secondsÚWINDOW_HÚ	IN_PLAY_Hr:   Úsortr<   Ú	MAX_BOOKSÚ
MAX_PROBESZfind_marketr\   ÚvaluesÚnextÚiterra   Zget_bborh   ÚallÚsumÚGROSS_GATE_C)ÚnowÚheldrr   Úcommitted_onlyÚ_ZmatchesZcandr?   ÚdivZkoÚhrsZpreZlive_finishrb   ZprobesZslugsr	   ZmkZ	ev_prefixZ	committedÚlegsÚbr
   r   rq   r   Úbooks_in_windowÞ   sd    


"&&"*r‹   c                 C   s  |sdS zt  ¡ pi  d¡pi }W n4 tyV } ztd|› �ƒ W Y d}~dS d}~0 0 d}| ¡ D �]®\}}ztt| d¡p€dƒƒ}W n tt	fy¢   Y qdY n0 |dkr®qd|  |¡pºi }| d¡rÞ| d¡pÒd|d krÞqdd}	zBt| d	¡pòi  d
¡ƒ}
|dk�r"|
dk�r"t
d|
|  dƒ}	W n tt	tf�yB   d}	Y n0 |	du �shd|	  k �rfdk �s‚n t| d¡�pxdƒ�p€d}	|	du �r¤td|› d|› d�ƒ qdt|	ƒ|d< ||d< tj tjj¡ ¡ |d< | d¡�säd|d< || |< |d7 }td|› d|› dt|	ƒd›�ƒ qd|S )a„  Mark legs we now HOLD as filled, so the book stops re-quoting them.

    WITHOUT THIS the maker forgets its own fills: `collected` stays 0, the leg
    is no longer resting so it looks unquoted, and the next cycle posts it
    AGAIN -- stacking short position on one outcome instead of working the two
    that would complete the book. poly_us_maker's MAX_CONTRACTS would cap the
    damage at 5 contracts, not prevent it.

    A post-only order can only ever fill AT OUR OWN LIMIT, so the price we
    posted IS the fill price -- no separate trade lookup is needed, which
    matters because this venue publishes no per-market trade tape.
    r   Z	positionsz2  [warn] positions unreadable, not marking fills: NZnetPositionDecimalrZ   re   rf   ÚcostÚvaluer   r0   ç        Úfloor_pxz  [WARN] position in z (z3) but no price could be derived; NOT marking filledZ	filled_atr   z(recovered)r/   z	  [FILL] ú z @ ú.2f)ÚpaZget_positionsr   rU   r   r]   Úabsrh   Ú	TypeErrorr9   ÚroundÚZeroDivisionErrorr1   rN   rƒ   rS   rT   Ú	isoformat)r=   r   ÚposÚeÚnr   rc   r„   rj   ÚpxrŒ   r   r   r   Údetect_fillsE  sN    
 

$
"rœ   c           #         sÜ  t ˆ|ƒ tƒ }z.t ¡ D ]}| d¡r| |d ¡ qd}W nz ty¸ } zbd}ttddƒ‰t ¡ ‰‡‡fdd„ˆ 	¡ D ƒ}t
dt|ƒd d	… › d
t|ƒ› d�ƒ W Y d }~n
d }~0 0 ttddƒ‰t ¡ ‰ |‡ ‡fdd„ˆ 	¡ D ƒO }d }}tˆƒ}	t| |	ˆ|�p
td�D �]À\}
}‰}dd„ ˆ 	¡ D ƒ‰tˆ ¡ ƒd d }d}ˆ 	¡ D ]h\}\‰}}ˆ ˆ¡�pli }| d¡}|�rPt| d¡�pŽdƒ}|t|ƒt�r®t|tƒt nd 7 }�qP|tk �rÚ|dk�rÚ|d7 }�qdt }‡fdd„‰‡‡fdd„ˆD ƒ}|| }t‡fdd„|D ƒƒ�p$d}|| }|D �]š}ˆ| \‰}}ˆ|v �rT�q2|dk�rf|| n|}ˆ ˆ¡�pxi  d¡}|�rž|dk�ržt|t|ƒƒ}t|dƒ}|| d }|td k�rÐ|dk�rÐ�q2d |  k�rèd!k�sîn �q2d} |�r"tˆ ˆ¡�pi  d¡�pdƒ}!tt|! d"ƒ}"|"d#k�r4�q2zt ˆ|"|¡ W nT t�yš } z:t
d$ˆ› d%t|ƒj› d%|› �ƒ W Y d }~�q2W Y d }~n
d }~0 0 t d&¡ z"t‡fd'd„tjdd(�D ƒƒ} W nL t�y } z2d} t
d)ˆ› d*t|ƒd d+… › d,�ƒ W Y d }~n
d }~0 0 t d-¡ nd } | �rˆ|d7 }| ˆ¡ ˆ ˆ¡�pLi }tt| d¡�p`dƒ|ƒ|d< |
|d.< t ¡ |d/< |ˆˆ< t|   ¡ |
ˆ||d0›|d0›|d0›|d1›|d1›|d0›|d0›| |d2œƒ �q2�q||fS )3NÚ
marketSlugFTÚMAKER_TTL_SéZ   c                    s<   h | ]4\}}t |tƒr| d ¡rˆ t|d  ƒ ˆk r|’qS ©Ú	posted_ts©r^   r_   r   rh   ©r   Zslrc   )Ú_now_tsÚ_ttlr   r   Ú	<setcomp>§  s   þzcycle.<locals>.<setcomp>z!  [warn] open orders unreadable (éP   z) -- DEGRADED: zH legs assumed resting from our own posts, finishing committed books onlyc                    s<   h | ]4\}}t |tƒr| d ¡rˆ t|d  ƒ ˆk r|’qS r    r¢   r£   )Ú_now_allÚ_ttl_allr   r   r¦   Â  s   þr   )r…   c                 S   s   i | ]\}}||d  “qS )r0   r   )r   Úsrc   r   r   r   r"   Ê  r#   zcycle.<locals>.<dictcomp>r   rt   rŽ   rZ   re   r/   c                    s6   ˆ   | ¡pi }|  d¡sdS t|  d¡p*dƒtd k S )NrZ   Tre   r   rf   rg   )r   rj   )r=   r   r   Ú_opené  s    
zcycle.<locals>._openc                    s    g | ]}ˆ ˆ| d  ƒr|‘qS )r   r   ©r   rª   )r«   r‰   r   r   Ú
<listcomp>î  r#   zcycle.<locals>.<listcomp>c                 3   s   | ]}ˆ | V  qd S ©Nr   r¬   )Úasksr   r   rs   ð  r#   zcycle.<locals>.<genexpr>r�   r0   éd   g{®Gáz”?g
×£p=
ï?é   rf   z	  [skip] ú: g      ø?c                 3   s   | ]}|  d ¡ˆ kV  qdS )r�   Nr   ©r   Úo)r   r   r   rs   >  s   ÿ)Zmax_agez	  [warn] z: posted but cannot confirm (é<   z) -- assuming restedg333333ã?r   r¡   r‘   z.1fr   )!rœ   r\   ÚpmÚ_ordersr   r`   rU   ÚgetattrrR   r]   r   rP   r<   rd   r‹   ÚFINISH_ONLYr�   r}   rh   ri   Úminr‚   ÚTARGET_MARGINÚmaxr•   ÚMAX_ABOVE_TOUCHZplace_maker_askÚtypeÚ__name__ÚsleepÚanyr.   r—   )#rƒ   r=   r   Zresting_slugsr´   Zdegradedr™   ÚpostedÚgatedr„   Zlabelr‡   rˆ   r
   r   rª   Z_bZ_arj   ÚfÚqÚtargetZ	open_legsr   Zopen_ask_sumZscaleZbidZaskZfloorZlatchedr›   Zabove_cr   Z_doneZwantr   )	r¨   r¤   r«   r¥   r©   r¯   r‰   r   r=   r   Úcycleˆ  s¾    

ÿ ÿ
(	



 
 $

ÿ
ÿ 
úrÇ   c                  C   s  dt jv } dt jv ad}dt jv rRt j d¡}|d tt jƒk rRtt j|d  ƒ}t ¡ rntdt› d�ƒ dS |dk}tr‚td	ƒ td
| › dt	› dt
› dt› dtj› d|r®dn
t|ƒd › �ƒ | sÌtdƒ t | ¡ | rîtdt d¡› �ƒ tƒ }t|ƒ}|�rtd|› dt› d�ƒ t ¡ |d  }tj ¡ }�z¤t�sª|�sPt ¡ |k �rªt ¡ �rntdt› d�ƒ �qªtj ¡ |k�r¬tj ¡ }t|ƒ}|�r¬td|› dt› d�ƒ tj tjj¡}	zt|	|| ƒ\}
}W nH t�y } z.tdt|ƒj › d|› �ƒ d }
}W Y d }~n
d }~0 0 t!|ƒ z&t "¡ }t|ƒ}t#dd„ |D ƒƒ}W n" t�yj   dt$dƒ }}Y n0 t|	 %d ¡› d!|
› d"|› d#|› d$|d%›�	d&d'� t &t'¡ �q6W | �rÆtd(t d)¡› �ƒ t d*¡ t!|ƒ n.| �rôtd(t d)¡› �ƒ t d*¡ t!|ƒ 0 td+ƒ dS ),Nz--livez--finish-onlyrµ   z	--minutesr/   zrefusing to start: z existsr   z?  FINISH-ONLY: no new books; working held ones until they closezpoly-us maker starting  LIVE=z  size=z	  books<=z	  window=zh  ttl=zs  runtime=Z	UNBOUNDEDr?   z6DRY RUN -- no orders will be sent. Pass --live to arm.zstartup cancel-all: Zstartupzpruned z state entries older than r@   z[KILL] z present -- stoppingz  [warn] cycle failed: r²   c                 s   s   | ]}t  |¡V  qd S r®   )r¶   Z_order_notionalr³   r   r   r   rs   §  r#   zmain.<locals>.<genexpr>éÿÿÿÿÚnanz%H:%M:%Sz posted z  gated z
  resting z  notional $r‘   T)Úflushzshutdown cancel-all: ZshutdownFZdone)(ÚsysÚargvr¹   Úindexr<   r7   Ú	KILL_FILEr'   r   ri   r{   rx   r¶   rž   rP   Zset_liveZ
cancel_allrG   rA   r4   rR   r1   r2   r3   r   rN   rƒ   rS   rT   rÇ   rU   r¾   r¿   rL   r·   r�   rh   ÚstrftimerÀ   Ú	POLL_SECS)r   ZminsÚiZforeverr=   Zn_prunedZdeadlineZ
last_prunerš   rƒ   rÂ   rÃ   r™   ZrowsZn_restZnotionalr   r   r   Úmaine  s”    
	

ÿÿÿþ



ÿÿÿ

ý

rÒ   Ú__main__)N):Ú__doc__r)   rN   r1   rC   ÚreÚsignalrË   rR   Úpathlibr   ÚpathÚinsertZpoly_us_pricesrw   Zpoly_us_makerr¶   Zpoly_us_traderZputZpoly_us_authr’   Zlive_monitorru   ZROOTrÎ   rE   r$   rx   rÐ   ri   r¹   r{   r‚   r»   r½   r*   r   r   ÚSIGINTr.   r4   Úcompiler5   rA   rG   rL   rX   r|   ry   rd   rk   Ú	frozensetr‹   rœ   rÇ   rÒ   r¿   Úexitr   r   r   r   Ú<module>   s`   	

 
	gC ^Q
