a
    y¬xjÐ^  ã                   @   sp  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e
dƒZe
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d„ Zdd„ Z dZ!dZ"d=dd„Z#i Z$dZ%dd„ Z&dd „ Z'g d!¢Z(d"d#„ Z)d$d%„ Z*d&d'„ Z+d(d)„ Z,d*d+„ Z-d,d-„ Z.d.d/„ Z/dZ0d0Z1d1a2da3d2d3„ Z4d4d5„ Z5d6d7„ Z6d8d9„ Z7d:d;„ Z8e9d<k�rle8ƒ  dS )>a›  PAPER market maker for POLYMARKET US. Places NOTHING. Ever.

Parallel to mm_quoter.py (Kalshi) so the two venues can be compared on identical
rules, identical fair values, and the same 0-5c price ladder. Everything that
differs is a property of the venue, not of the strategy.

WHY POLY-US IS WORTH TRACKING SEPARATELY. On the numbers, it should be a better
venue for this strategy on every axis we can measure:

  MAKER REBATE, not a fee. Poly-US credits the maker 0.0125*C*p*(1-p) at fill
    (docs.polymarket.us/fees, effective 2026-07-01) where Kalshi pays exactly
    zero. On a typical three-leg book (54/27/21) that is +0.76c/contract.
  CHEAPER TAKER for the completions we cannot avoid: 0.06*p*(1-p) vs Kalshi 0.07.
  Together these drop the break-even gross from +2.38c (Kalshi) to +1.42c. That
    matters because the 1c tick quantises gross into 0c/1c/2c/3c buckets -- on
    Kalshi only 3c books clear (2 of our first 9); on Poly-US ordinary 2c books
    do, which roughly triples the eligible population.
  SHALLOW BOOKS: depth of 7-20 contracts vs Kalshi's 1,749-44,427. Our central
    unsolved problem on Kalshi is queue position -- with 44k ahead of us we are
    never reached. Here we would rest near the front.

SAFETY. This module imports poly_us_prices (read-only market data) and never
poly_us_trader. There is no code path in this file that can place an order.

FILL MODEL, and its known bias. Poly-US has no public per-market trade feed on
the retail API (candlesticks live on the institutional host, which our key is
not authorised for -- verified 2026-07-29). So a fill is inferred from `currentPx`,
the last trade price returned by the BBO endpoint: if at any poll currentPx has
reached our ask, someone traded there and a resting order would have been hit.
This is COARSER than the Kalshi model in one direction and more conservative in
another: we only see the LAST trade, so a print above our ask that reverts inside
a 20-second poll is invisible, which UNDER-counts fills. Kalshi's model, by
contrast, sees every print and so over-counts. Do not compare raw fill rates
across the two venues without accounting for that.

Usage:  python main\mm_quoter_polyus.py [--once]
é    N)ÚPathzE:\soccer\mainz'E:\soccer\research\mm_polyus_quotes.csvz'E:\soccer\research\mm_polyus_state.jsong      (@é   é
   ç       @ç      ð?ç      @g      .@g     @@zpus-v1g¸…ëQ¸®?gš™™™™™‰?c                 C   s   t |  d|   S ©Né   )ÚTAKER_THETA©Úp© r   ú"E:\soccer\main\mm_quoter_polyus.pyÚ	taker_feeM   s    r   c                 C   s   t |  d|   S r   )ÚMAKER_THETAr   r   r   r   Úmaker_rebateQ   s    r   T))g      "@ç      @)ç      @ç      @)r   ç      @)r   r   )ç      ø?r   Ú ç     ÀX@c                 C   s`   d}t D ]\}}||kr|} q"qtt | › d|› � ¡ ¡ ¡ dƒd d }tdtd|| ƒƒS )aS  Premium in cents over the market ask, decaying toward the touch.

    A per-leg offset of -1/0/+1 (md5-stable) is kept so that at any given time
    different legs sit at different rungs. Without it, price and time-to-kickoff
    would be perfectly confounded and the fill curve could never be separated
    from a time-of-day effect.
    ç        ú|é   é   r	   r   )ÚDECAYÚintÚhashlibZmd5ÚencodeZ	hexdigestÚmaxÚmin)ÚeventÚsideÚhrsÚbaseZcutÚvalZoffr   r   r   Úarm_form   s    *r(   é<   c                 C   s”   | ||t |ƒf}t |¡}|r:t ¡ |d  tk r:|d S z.ddl}|jt |ƒ||t | ƒ ¡ d�pdi }W n ty~   i }Y n0 t ¡ |ft|< |S )zG{'home_bid':..,'home_ask':..,...} from Polymarket.com, or {} if absent.r   r	   N)Zleague)	ÚstrÚ	_PC_CACHEÚgetÚtimeÚ_PC_TTLZpolymarket_liveZget_all_pricesÚupperÚ	Exception)ÚdivÚhomeÚawayÚ	game_dateÚkeyZhitZ_plÚrr   r   r   Úpoly_com_prices‹   s    
&
r7   c                 C   s4   | dkrdS | dkrdS | dkr$dS | dkr0dS dS )	Nr   r   r   r   r   g      à?r   r   r   )r%   r   r   r   Ú
margin_forš   s
    r8   ©Útsr1   r2   r3   ÚkickoffÚ
event_slugÚmarket_slugr$   Úpin_fairZp_bidZp_askZlast_pxÚover_askÚour_askÚcompetitiveÚsizeZ	bid_depthZ	ask_depthZrebate_cÚreasonÚarmÚregimeZpc_bidZpc_askc              	   C   sP   zt j t| ƒ dd¡¡}W n ttfy4   Y d S 0 |jr@|S |jt jj	d�S )NÚZz+00:00©Útzinfo)
ÚdtÚdatetimeÚfromisoformatr*   ÚreplaceÚ
ValueErrorÚ	TypeErrorrH   ÚtimezoneÚutc)ÚsÚdr   r   r   Ú_pt¨   s
    rS   c                    s(   dd„ | D ƒ}t |ƒ‰ ‡ fdd„|D ƒS )Nc                 S   s   g | ]}d | ‘qS )r   r   ©Ú.0Úxr   r   r   Ú
<listcomp>±   ó    zdevig.<locals>.<listcomp>c                    s   g | ]}|ˆ  ‘qS r   r   rT   ©rQ   r   r   rW   ³   rX   )Úsum)Úor6   r   rY   r   Údevig°   s    r\   c                   C   s8   t  ¡ r4zt t jdd�¡W S  ty2   i  Y S 0 i S )Núutf-8©Úencoding)ÚSTATEÚexistsÚjsonÚloadsÚ	read_textr0   r   r   r   r   Ú
load_state¶   s    
re   c                 C   s   t jtj| dd�dd� d S )Nr	   )Úindentr]   r^   )r`   Z
write_textrb   Údumps©Ústr   r   r   Ú
save_state¿   s    rj   c                 C   sT   t  | ||¡}|sdS | d¡s4| d¡s4| d¡r8dS | d¡pDd ¡ }|tjvS )a«  Poly-US's own view of whether this match is on.

    Mirrors live_monitor.NOT_PLAYED against the event's `period` field, which
    carries API-Football-style short codes ('NS', '1H', 'FT', 'PST', ...).
    Unknown/absent -> True, so a missing field degrades to "quote it" and the
    API-Football backstop still has the last word, rather than silently
    stopping us on every event whose period Poly-US has not populated.
    FZendedÚclosedZarchivedÚperiodr   )ÚpricesÚ
find_eventr,   r/   ÚLMZ
NOT_PLAYED)r1   r2   r3   Úerl   r   r   r   Úpolyus_playableÃ   s    	rq   c           
         sÖ   t  | ||¡}|sdS dd„ | d¡p(g D ƒ}t|ƒdkr>dS i tƒ  }‰ d|fd|ffD ]<\}}t  | |||¡}|s| dS |d ||< ˆ  |d ¡ qZ‡ fd	d„|D ƒ}	t|	ƒd
krºdS |	d |d< || dd¡fS )aS  {'home': slug, 'draw': slug, 'away': slug} or None.

    find_market() only knows home/away, so the draw is taken as the remaining
    market once both teams are matched -- and we require EXACTLY three markets
    and both teams identified before accepting, so a partial match can never
    silently assign the wrong slug to the draw.
    Nc                 S   s&   g | ]}t | d d¡ƒ d¡r|‘qS )Úslugr   zatc-)r*   r,   Ú
startswith©rU   Úmr   r   r   rW   ä   s   ÿz resolve_legs.<locals>.<listcomp>Zmarketsr   r2   r3   rr   c                    s    g | ]}|d  ˆ vr|d  ‘qS )rr   r   rt   ©Zusedr   r   rW   ï   rX   r	   r   Údrawr   )rm   rn   r,   ÚlenÚsetZfind_marketÚadd)
r1   r2   r3   rp   ZmkÚoutr$   Zteamru   Úrestr   rv   r   Úresolve_legsÕ   s$    r}   c                 C   sH   t | ƒD ]:}t| |  d¡ƒ}|du s6||  ¡ dkr|  |d¡ q| S )aõ  Drop legs whose kickoff is >4h past -- the match has finished and the
    paper position has settled.

    ADDED 2026-07-30. The Kalshi quoter did this from the start; this one did
    not, so state accumulated every book forever -- 12 stale books from the
    previous day were still being carried, some up to 23h past kickoff. Nothing
    is lost by dropping them: mm_polyus_quotes.csv logs every leg every cycle and
    is the durable record. State is only the set of CURRENTLY resting orders.
    r;   Ni@8  )ÚlistrS   r,   Útotal_secondsÚpop)ri   ÚnowÚkÚkor   r   r   Úrelease_expiredö   s
    
r„   i�  r   c                  C   sh   ddl } |   ¡ t dk rtS z(t ¡ }|tkatsBtd|› d�ƒ W n tyZ   daY n0 |   ¡ atS )zBFalse when API-Football is nearly spent. Cached 5 min; fails OPEN.r   Ni,  z  [quota] API-Football down to z -- pausing quotingT)r-   Ú_quota_checked_atÚ	_quota_okro   Zget_api_remainingÚAF_QUOTA_FLOORÚprintr0   )Ú_tZ	remainingr   r   r   Úquota_ok  s    
rŠ   c           ,         sÄ  t ƒ s
dS t ¡ \}}tj tjj¡}tt	ƒ |ƒ}d }}|D �]r}| 
d¡pPd ¡ }	|	tjvrbq>| 
d¡}
t|
tƒr‚tj |
¡}
zTt|d ƒ d¡d d… \}}tj|
j|
j|
jt|ƒt|ƒt tjd	d
�¡d�}W n tyì   Y q>Y n0 ||  ¡ d }d|  k �rtk�sq> q>t|	|d |d ƒ}|�s6q>|\}}t|	|d |d ƒ�sVq>tj| 
d¡tjd��spq>i ‰ | ¡ D ]¶\}}t  |¡}|�r¬| 
d¡�r¬| 
d¡�s¶i ‰  �q4d|d   k �râ|d   k �râdk �sîn i ‰  �q4t!t"| 
d¡�p dƒt"| 
d¡�pdƒƒt#k �r(i ‰  �q4|ˆ |< �q|ˆ �s<q>t$‡ fdd„ˆ D ƒƒ}t$‡ fdd„ˆ D ƒƒ}|| d t%k�r|q>|d d t&k �r�q>t '| 
d¡¡\}‰ˆr>t(‡fdd„dD ƒƒ�sÂq>t)ˆd ˆd ˆd gƒ}t*|d |d |d d �}t+|	|d |d |
ƒ}| ¡ D �]ª\}}ˆ | }t,|||ƒ}|| d }|t-|ƒ }t.|d d d!ƒt/�rd|nd" }t.t!||ƒd!ƒ} | |t0 k }!dd l1}"|!�s¢|" 2| ¡d# nd }#|! �oÆ|#d u�oÆ|#|d d$ k}$|!�sÒdn$d%|d d&›d'|| d(›d)t0d*›d+�}%|}&|&|v �r ||&  
d,¡�s |#d u�r ||& d- }'|t3k�r>|d n|#}(|" 2t.|(d d!ƒ¡d# }(|(|'d$ k �r |(||& d-< | 4¡ ||& d.< d||&  
d/d¡ ||& d/< |!�sî|&|v�rî|||#t.|| d0ƒ| 4¡ d1d1|t5|	|d |d | 4¡ d2œ||&< |  6| 4¡ |	|d |d | 4¡ |||t.|| d0ƒ|d |d | 
d3¡t.|d0ƒ|#�rJt.|#dƒndt|$ƒt7| 
d¡| 
d¡|#�r|t.t8|#ƒd d4ƒnd|%|t5| 
|› d5�d¡| 
|› d6�d¡d7œ¡ ||$�r¸dnd7 }�q| ¡ D ]¢\}}| 
|¡})|)�rÊ|) 
d,¡�sÊ|) 
d8¡�rþ�qÊˆ |  
d3¡}*|*d u�rÊ|*|)d- d$ k�rÊd9|)d,< |)d- |)d:< | 4¡ |)d;< |)d- }+t.t8|+ƒd0ƒ|)d<< d=|)d>< �qÊ|t9||ˆ | |||||	|ƒ
7 }|t:||ˆ | |||||	|ƒ
7 }| ;¡  q>t<|ƒ ||fS )?N)r   r   r   Zlcr   r4   r;   ú:é   éüÿÿÿ)ZhoursrG   g      ¬@r2   r3   Zfid)Zmax_ageÚbestBidÚbestAskr	   ZbidDepthZaskDepthc                 3   s   | ]}ˆ | d  V  qdS )r�   Nr   ©rU   rQ   ©Úbookr   r   Ú	<genexpr>m  rX   zcycle.<locals>.<genexpr>c                 3   s   | ]}ˆ | d  V  qdS )rŽ   Nr   r�   r‘   r   r   r“   n  rX   éd   r   c                 3   s   | ]}ˆ   |¡V  qd S )N©r,   )rU   r‚   )Úpinr   r   r“   u  rX   )ÚhrR   Úar—   rR   r˜   )r2   rw   r3   é   r   g      Y@g•Ö&è.>zmarket ask ú.2fz below our fair z.3fz by > z.1fzc -- skippedÚfilledr@   Zrepriced_atZreprice_counté   F)r#   r$   r@   r>   r;   r›   Ú	completedrD   rE   r1   r2   r3   Z	posted_atZ	currentPxr   Z_bidZ_askr9   Úbook_closedTÚfill_pxÚ	filled_atÚrebateZnaturalÚ	fill_kind)=rŠ   ro   Zload_watchlist_filerI   rJ   r�   rO   rP   r„   re   r,   r/   rm   ZLEAGUE_SLUGÚ
isinstancer*   ÚdaterK   ÚsplitZyearZmonthZdayr   Z	timedeltar0   r   ÚWINDOW_Hr}   rq   Zfixture_playableZPLAYABLE_BACKSTOP_TTLÚitemsZget_bbor"   ÚfloatÚMIN_LEG_DEPTHrZ   ÚMAX_SPREAD_CÚGROSS_GATE_CZfetch_prematch_oddsÚallr\   Údictr7   r(   r8   ÚroundÚ
AB_ENABLEDÚFAIR_TOLERANCE_CÚmathZceilÚCOMPLETE_BEFORE_KO_HÚ	isoformatÚREGIMEÚwriterowÚSIZEr   Úcomplete_bookÚflatten_stalledÚflushrj   ),ÚwriterÚfhÚ_Zmatchesr�   ri   Zquotedr�   ru   r1   ÚgdZhhÚmirƒ   r%   ZgotÚlegsr<   r$   rr   Úbr?   Zover_bidZb365ZfairZfmapZpcZbbrD   Zfair_cZtarget_cZ	ceiling_cZour_cÚskipr±   ZourZcomprC   r5   ZcurÚtgtrQ   Zlastr   r   )r’   r–   r   Úcycle+  sþ    



ÿ


.ÿÿ
 "
ÿÿÿ$
ü

õ

rÃ   c
                    sÊ  ‡ fdd„|  ¡ D ƒ}
t|
ƒdkr&dS ‡ fdd„|
D ƒ}t|ƒdksL|tkrPdS |d }ˆ |  d¡stˆ |  d¡rxdS ˆ | d	 }|| d
 }d|  k r¤dk sªn dS ˆ | d }|| d t|ƒd  }|
D ]}dˆ | d< qÒˆ |  dt|dƒt|dƒ| ¡ dœ¡ | | ¡ ||d |d | ¡ |	||t|dƒdt	ˆ |  d¡t
d|› d|d›d|d›dt|ƒd d›d|d›d�dœ¡ td|› d|d › d|d › d |› d!|d›d"|d›d#|d›d�ƒ dS )$aº  Book stuck at 1-of-3 near kickoff: buy back the one sold leg, go flat.

    Mirror of mm_quoter.flatten_stalled -- see the long note there for why this
    exists separately from complete_book (which only fires at exactly 2-of-3) and
    why a slightly negative-EV fee is worth paying to stop naked legs burying the
    signal. Poly-US differs only in the fee (0.06 vs 0.07) and in the book being
    keyed by side rather than by ticker.
    c                    s   g | ]}|ˆ v r|‘qS r   r   ©rU   Zs_rh   r   r   rW   Ì  rX   z#flatten_stalled.<locals>.<listcomp>r   r   c                    s   g | ]}ˆ |   d ¡r|‘qS ©r›   r•   rÄ   rh   r   r   rW   Ï  rX   r	   Ú	flattenedr�   r$   r�   r@   r”   Trž   rŒ   )rÆ   Z
flatten_pxZflatten_cost_cZflattened_atr2   r3   rD   zFLATTEN_1OF3: bought back z sold @rš   z at ask z, fee z
c => cost ú+.1fz
c/contract)r:   r1   r2   r3   r;   r<   r=   r$   r@   rA   rB   rD   rE   rC   z    [FLATTEN] ú ú vs z: bought back z @z (sold z) -> )Úvaluesrx   r²   r,   r   Úupdater®   r³   rµ   r¶   r´   rˆ   )ri   r¿   r’   rº   r�   rƒ   r%   ru   r1   r<   Úminer›   rÂ   r$   ZaskZsoldZcost_cr‰   r   rh   r   r¸   Ã  sT    	þ
ÿÿþû$ÿÿÿ
r¸   c
                    sÆ  ‡ fdd„|  ¡ D ƒ}
t|
ƒdkr&dS t‡ fdd„|
D ƒƒr@dS ‡ fdd„|
D ƒ}t|ƒdksf|tkrjdS |d }ˆ | d	 }|| d
 }d|  k rždk s¤n dS t‡ fdd„|
D ƒƒ| }t‡ fdd„|
D ƒƒ}|d | d t|ƒd  }ˆ |  dd|| ¡ ddœ¡ | | ¡ ||d |d | ¡ |	||t	|dƒdt
d|d d›d|d d›dt|ƒd d›d|d›d�	ˆ |  d¡tdœ¡ td|› d |d › d!|d › d"|› d#|d›d$|d›d�ƒ dS )%a  Cross the last unsold leg into the bid, but only near kickoff.

    Same rule as Kalshi (see mm_quoter.complete_book): firing the instant a book
    hits 2-of-3 cost ~2c per book for nothing, because the third leg still had
    most of its window to sell for free.
    c                    s   g | ]}|ˆ v r|‘qS r   r   r�   rh   r   r   rW   ö  rX   z!complete_book.<locals>.<listcomp>r   r   c                 3   s   | ]}ˆ |   d ¡V  qdS )rž   Nr•   r�   rh   r   r   r“   ù  rX   z complete_book.<locals>.<genexpr>c                    s   g | ]}ˆ |   d ¡s|‘qS rÅ   r•   r�   rh   r   r   rW   û  rX   r	   r$   rŽ   c                 3   s(   | ] }ˆ |   d ¡rˆ | d V  qdS )r›   r@   Nr•   r�   rh   r   r   r“     rX   c                 3   s,   | ]$}ˆ |   d ¡rˆ |   dd¡V  qdS )r›   r¡   r   Nr•   r�   rh   r   r   r“     rX   r   r”   Tr�   )r�   r›   rŸ   r    r¢   r2   r3   rŒ   zCOMPLETE_AT_BID: collected z.0fzc, rebates rš   zc, fee zc => rÇ   zc/contract lockedrD   )r:   r1   r2   r3   r;   r<   r=   r$   r@   rA   rB   rC   rD   rE   z    [COMPLETE] rÈ   rÉ   z: sold z at bid z -> )rÊ   rx   Úanyr²   rZ   r   rË   r³   rµ   r®   r¶   r,   r´   rˆ   )ri   r¿   r’   rº   r�   rƒ   r%   ru   r1   r<   rÌ   ZunfilledrÂ   r$   ZbidZ	collectedZrebatesZnet_cr   rh   r   r·   ï  sL    ÿ
ÿÿþù$ÿÿ
r·   c               
   C   sþ   dt jv } t ¡  }ttdddd�}tj|td�}|r>| ¡  t	dt
› dt› d	t› d
�ƒ zBt||ƒ\}}t	tj ¡ d›d|› d�|r’d|› d�nd ƒ W nJ tyæ } z2t	tj ¡ d›dt|ƒj› d|› �ƒ W Y d }~n
d }~0 0 | rîqút t¡ qZd S )Nz--oncer˜   r   r]   )Únewliner_   )Z
fieldnamesz%PAPER Poly-US quoter running (window zh, ladder 0-5c, size z, maker REBATE z). Places nothing.z%H:%M:%Sz  logged quotes on z legsz, completed z book(s)z  cycle error: z: )ÚsysÚargvÚOUTra   ÚopenÚcsvZ
DictWriterÚFIELDSZwriteheaderrˆ   r¦   r¶   r   rÃ   rI   rJ   r�   r0   ÚtypeÚ__name__r-   ÚsleepÚ	POLL_SECS)ÚonceÚnewr»   ÚwÚnÚcrp   r   r   r   Úmain  s*    


ÿÿ
ÿ
<rÞ   Ú__main__)r   r   ):Ú__doc__rÓ   rJ   rI   r   rb   r±   rÏ   r-   Úpathlibr   ÚpathÚinsertZpoly_us_pricesrm   Zlive_monitorro   ÚmapsrÑ   r`   r¦   rØ   r¶   r°   r²   r«   rª   ZBANKROLLr´   r
   r   r   r   r¯   r   r(   r+   r.   r7   r8   rÔ   rS   r\   re   rj   rq   r}   r„   r©   r‡   r…   r†   rŠ   rÃ   r¸   r·   rÞ   rÖ   r   r   r   r   Ú<module>   sl   %
	! ,&
