a
    tšgjË%  ã                   @   s‚   d Z ddlZddlZddlZddlmZ dZdZdZ	dd„ Z
d	Zd
d„ Zdd„ Zdd„ Zdd„ Zdd„ Zdd„ Zedkr~eƒ  dS )aš  Does gating on a book's GROSS ask-margin help, historically?

Today's 7 paper books suggested: only post a 3-leg book if sum(asks) - 100 covers
the ~2.4c it costs to complete the last leg (1c crossing + taker fee). That
threshold was fitted to 7 points, so test the mechanism on the 641 settled
events in kalshi_backfill.csv.

THE TAUTOLOGY TRAP, stated up front: if all three legs fill, book P&L IS
sum(asks) - 100. Gating on gross would then "work" by definition and prove
nothing. The only real question is whether high-gross books still FILL -- a wide
book is wide because it is illiquid, and gating toward wide books may select
books nobody trades, leaving 2-of-3 partial books, which is the dangerous state.
So the test here is fill behaviour vs gross, not P&L vs gross.

KNOWN LOOK-AHEAD (do not paper over it): kalshi_backfill snaps the LAST candle
at or before kickoff, but we would post hours earlier. Using that near-kickoff
ask as our posting price and then counting trades over the whole pre-KO window
counts fills at prices the book only reached later -- it inflates fill rates.
Mitigated by reporting a NEAR window (last 3h) alongside the full window; the
near window is the honest one. This is the same class of error that produced the
83%-vs-19% fill bug in the paper maker.

MAKER-SELL SEMANTICS (verified against our own fills): taker_book_side='bid'
means the taker BOUGHT, i.e. a maker SOLD. Those are our fills.
é    N)Údefaultdictz&E:\soccer\research\kalshi_backfill.csvz E:\soccer\research\mm_trades.csvç      @c                 C   s   d|  d|   d S )NgìQ¸…ë±?é   éd   © )Úpr   r   ú+E:\soccer\research\mm_gross_gate_history.pyÚ<lambda>"   ó    r	   g      @c                 C   sP   | sd S zt j |  dd¡¡}W n ty4   Y d S 0 |jr@|S |jt jjd�S )NÚZz+00:00)Útzinfo)ÚdtÚdatetimeZfromisoformatÚreplaceÚ
ValueErrorr   ÚtimezoneZutc)ÚsÚdr   r   r   Ú_pt.   s    r   c                     s,  i } t tdd���}t |¡D ]è‰z(‡fdd„dD ƒ‰ ‡fdd„dD ƒ‰W n ttfyd   Y qY n0 tˆd ƒ}|d u stdd	„ ˆ  ¡ D ƒƒs’qt	‡ ‡fd
d	„ˆ D ƒƒd }|t
ks|dk rÀqt	ˆ ¡ ƒdkrÒqˆd |ˆ ˆˆd |t	ˆ  ¡ ƒd d dœ| ˆd < qW d   ƒ n1 �s0    Y  | S )Núutf-8©Úencodingc                    s"   i | ]}|t ˆ d |› d� ƒ“qS )Úk_Z_ask©Úfloat©Ú.0r   ©Úrr   r   Ú
<dictcomp>=   r
   zload_books.<locals>.<dictcomp>©ÚhomeÚdrawÚawayc                    s"   i | ]}|t ˆ d |› d� ƒ“qS )r   Z_bidr   r   r   r   r   r   >   r
   Zkickoffc                 s   s&   | ]}d |  k odk n  V  qdS )r   r   Nr   )r   Úar   r   r   Ú	<genexpr>B   r
   zload_books.<locals>.<genexpr>c                 3   s   | ]}ˆ | ˆ|  V  qd S ©Nr   r   )ÚasksÚbidsr   r   r%   D   r
   r   r   ç      ð?ÚdivÚftr)r*   Úkor'   r(   r+   ÚspreadÚgrossZevent)ÚopenÚBACKFILLÚcsvÚ
DictReaderr   ÚKeyErrorr   ÚallÚvaluesÚsumÚMAX_SPREAD_C)ÚbooksÚfhr,   r-   r   )r'   r(   r   r   Ú
load_books8   s*    
	

þ0r:   c           
   
   C   s  dd„ |   ¡ D ƒ}ttƒ}ttdd��Ø}t |¡D ]¼}|d }| d¡}|dk rRq2|d|… }| |¡}|du rrq2|d	 d
kr€q2t	|d ƒ}	|	du s2|	|kržq2z8||||d d… f  
|	t|d ƒt|d ƒf¡ W q2 tyì   Y q2Y q20 q2W d  ƒ n1 �s0    Y  |S )z@Per (event, leg-suffix): maker-sell trades, bounded pre-kickoff.c                 S   s   i | ]\}}||d  “qS )r,   r   )r   ÚeÚbr   r   r   r   Y   r
   zload_fills.<locals>.<dictcomp>r   r   Ztickerú-r   NZtaker_book_sideZbidZtsr   ZpriceÚcount)Úitemsr   Úlistr/   ÚTRADESr1   r2   ÚrfindÚgetr   Úappendr   r   )
r8   ZwantÚoutr9   r   ZtkÚiÚevr,   Útr   r   r   Ú
load_fillsW   s,    

8,rI   c                    sZ  i }|   ¡ D �]F\‰}t‡fdd„|D ƒƒ}t|ƒdkr:qdd„ |D ƒ‰ tˆ ƒdkrVq‡ fdd„|D ƒ}i }|D ]0}dd„ |ˆ|f D ƒ}|r˜t |¡nd	||< qptd
d„ | ¡ D ƒƒrºq|\}	}
t||	 |d d  ƒt||
 |d d  ƒ }t||
 |d d  ƒt||	 |d d  ƒ }||k�r4|	|
fn|
|	f\}}|ˆ d |dœ|ˆ< q|S )ap  Map each event's 3 sides to their ticker suffixes by matching ask prices.

    Suffixes are team codes of varying length, so string-slicing them is the bug
    that mis-assigned 24/1060 legs before. Instead: the draw leg's suffix is the
    one Kalshi tags TIE/DRAW; home/away are resolved by which observed trade
    prices sit closest to that side's quoted ask.
    c                    s   h | ]\}}|ˆ kr|’qS r   r   )r   r;   r   )rG   r   r   Ú	<setcomp>{   r
   zleg_suffixes.<locals>.<setcomp>é   c                 S   s   g | ]}|  ¡ d v r|‘qS ))ZTIEZDRAW)Úupperr   r   r   r   Ú
<listcomp>~   r
   z leg_suffixes.<locals>.<listcomp>r   c                    s   g | ]}|ˆ d  kr|‘qS )r   r   r   )r"   r   r   rM   �   r
   c                 S   s   g | ]\}}}|‘qS r   r   )r   Ú_r   r   r   r   rM   „   r
   Nc                 s   s   | ]}|d u V  qd S r&   r   )r   Úvr   r   r   r%   †   r
   zleg_suffixes.<locals>.<genexpr>r'   r!   r#   r   r    )r?   ÚsortedÚlenÚ
statisticsZmedianÚanyr5   Úabs)r8   ÚfillsÚresr<   ZsufsÚrestZmedr   Zpxr$   ÚcZd1Zd2r!   r#   r   )r"   rG   r   Úleg_suffixesq   s*    00rY   c                 C   sD   |t jtd� }| D ]*\}}}|r,||k r,q||d kr dS qdS )zFWould a resting sell at our_ask have been hit? Trade price >= our ask.)Zhoursg•Ö&è.>TF)r   Z	timedeltaÚNEAR_H)ZtradesZour_askr,   Z	near_onlyÚlorH   r   rN   r   r   r   Úfilled‘   s    r\   c                     s¸  t ƒ } t| ƒ‰t| ˆƒ}tt| ƒ› dt|ƒ› d�ƒ dD �]‰ˆrPdtd›d�nd}g }| ¡ D ]H\‰‰| ˆ ‰t‡‡‡‡‡fdd	„d
D ƒƒ}| ˆd |ˆˆˆdœ¡ q`td|› �ƒ tddd›ddd›ddd›ddd›ddd›ddd›�ƒ g d¢}|D �]J\‰‰‡‡fdd„|D ƒ}|�s"qütdd	„ |D ƒƒ}tdd	„ |D ƒƒ}tdd	„ |D ƒƒ}	g }
|D ]d}|d d k�r‚|
 |d ¡ n@|d d!k�r`|d" ‰t	ˆd#  
¡ ƒ}|
 |d d$ t|ƒ ¡ �q`|
�rÖt |
¡ntd%ƒ}ˆd&k�rüˆd›d'ˆd›d(�nd)ˆd›d(�}td|d›dt|ƒd*›d|d*›d|d*›d|	d*›d|d+›d(�ƒ qütƒ  q8td,ƒ tdd-d›dd.d/›dd0d1›dd2d1›dd3d/›�
ƒ g }| ¡ D ]ˆ\‰‰| ˆ ‰t‡‡‡‡fd4d	„d
D ƒƒ}|d k�rÖˆd }n0|d!k�rˆd d$ tt	ˆd#  
¡ ƒƒ }nd }| ˆd ||d5œ¡ �q–d6D ]Ž‰ ‡ fd7d„|D ƒ}d8d„ |D ƒ}
|
�sR�q$td9d	„ |D ƒƒt|ƒ d: }tdˆ d;›dt|ƒd<›dt |
¡d=›d>t|
ƒd?›d>|d@›dA�ƒ �q$d S )BNz settled books; z* with all 3 legs resolvable in trade data
)TFzNEAR window (last Úgz
h, honest)z(FULL pre-KO window (look-ahead inflated)c                 3   s4   | ],}t ˆˆˆ| f ˆ d  | ˆ d ˆƒV  qdS )r'   r,   N©r\   r   )r<   rG   rU   ÚnearÚsfxr   r   r%   §   s   ÿzmain.<locals>.<genexpr>r    r.   )r.   ÚnfrG   r<   r`   z--- z  zgross bucketz>16sú r8   z>6sz3/3z2/3z<=1zmean net c/contractz>21s))é�ÿÿÿr)   )r)   ç       @)rd   r   )r   ç      @)re   iç  c                    s,   g | ]$}ˆ|d    kr ˆ k rn q|‘qS ©r.   r   ©r   Úx)Úhir[   r   r   rM   ¯   r
   zmain.<locals>.<listcomp>c                 s   s   | ]}|d  dkrdV  qdS )ra   rK   r   Nr   rg   r   r   r   r%   ²   r
   c                 s   s   | ]}|d  dkrdV  qdS ©ra   é   r   Nr   rg   r   r   r   r%   ³   r
   c                 s   s   | ]}|d  dkrdV  qdS )ra   r   Nr   rg   r   r   r   r%   ´   r
   ra   rK   rk   r<   r(   r)   Únanrc   z to rX   z< Z6dz20.2fz>GATE SIMULATION (near window): post only books with gross >= TÚTzbooks postedz>13szmean netz>10sÚtotalz2/3 strandedc                 3   s4   | ],}t ˆˆˆ| f ˆ d  | ˆ d dƒV  qdS )r'   r,   TNr^   r   )r<   rG   rU   r`   r   r   r%   Ê   s   ÿ)r.   ra   Únet)rc   r   r   rk   g      @rK   é   c                    s   g | ]}|d  ˆ kr|‘qS rf   r   rg   )rm   r   r   rM   Ô   r
   c                 S   s    g | ]}|d  dur|d  ‘qS )ro   Nr   rg   r   r   r   rM   Õ   r
   c                 s   s   | ]}|d  dkrdV  qdS rj   r   rg   r   r   r   r%   Ø   r
   r   z6.1fZ13dz9.2fzc z9.1fz12.1fú%)r:   rI   rY   ÚprintrQ   rZ   r?   r6   rD   Úminr5   ÚFEErR   Zmeanr   )r8   ZsmapZlblZrowsra   Zbucketsr]   Zn3Zn2Zn1Znetsrh   ZworstZmnÚnamero   Zstrandr   )rm   r<   rG   rU   ri   r[   r_   r`   r   Úmainœ   s|    

ÿ,ÿ,@0ÿ


"&ÿÿrv   Ú__main__)Ú__doc__r1   r   r   rR   Úcollectionsr   r0   rA   rZ   rt   r7   r   r:   rI   rY   r\   rv   Ú__name__r   r   r   r   Ú<module>   s"   	
 A