a
    è»j‘u  ã                   @   sL  d 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	Z
dZdZdZdZd	Zed
ƒZdZedƒZg d¢Zdai Zi ZdZd2dd„Zeedœdd„Zeddœdd„Zedœdd„Zd3dd„Z dd„ Z!edœd d!„Z"e#dœd"d#„Z$eed$œd%d&„Z%defee#e#ee e#ed'œd(d)„Z&d*d+„ Z'd4eee#d,œd-d.„Z(d/d0„ Z)e*d1k�rHe)ƒ  dS )5aÑ  Maker-side order client: RESTING SELL orders, with a dead-man's switch.

SEPARATE FROM kalshi_trader.place_limit_order ON PURPOSE. That function buys
YES, immediate-or-cancel, capped at $1, and its comment says why: "never leave a
resting order sitting unmonitored" (project_no_subask_limits). xlow's auto-trade
depends on those properties. Market making needs the opposite -- sell YES, rest
in the queue between polls -- so it gets its own client rather than a flag that
weakens the one already in production.

THE RISK THIS IS BUILT AROUND. On 2026-08-10 this machine powered off and was
not noticed for two and a half days. Resting orders would have filled and
settled with no complete_book, no flatten_stalled, and nobody watching. Cancel
on startup and cancel on shutdown do not help: a power cut runs neither.

So every order carries `expiration_time`. If this process stops refreshing them,
the EXCHANGE cancels them within MAKER_TTL_S. The quoter re-posts every poll, so
the orders are continuously renewed while it is alive and evaporate when it is
not. That is the only safety property here that survives the machine dying.

FOUR MORE GUARDS, all enforced before any HTTP request:
  post_only          -- the exchange REJECTS the order if it would cross, so a
                        maker can never accidentally lift somebody's bid.
  cancel_on_pause    -- exchange-side cancel if the market halts.
  hard caps          -- contracts, price, and total resting collateral.
  LIVE off by default -- set_live(True) must be called explicitly; until then
                        every call is logged and returns without touching the
                        network.

Usage is via the quoter; run this file directly for the guard self-test.
é    N)ÚPath)ÚOptionali  é
   ç{®Gáz„?éc   é   Úinfé-   z%E:\soccer\research\mm_live_orders.csv©ZtsÚactionÚtickerÚsideÚcountÚprice_centsÚ
expires_atZliveÚorder_idÚstatusÚdetailFé   c                    s@   ˆ p
t   ¡ ‰ ‡ fdd„t ¡ D ƒ}|D ]}t |d¡ q&t|ƒS )zFDrop ids whose TTL has passed -- the exchange has already killed them.c                    s*   g | ]"\}}t |tƒr|d  ˆ kr|‘qS )r   ©Ú
isinstanceÚtuple©Ú.0ÚoidÚv©Únow© úkalshi_maker.pyÚ
<listcomp>Å   s   ÿz!_prune_placed.<locals>.<listcomp>N)ÚtimeÚ_PLACEDÚitemsÚpopÚlen)r   Zdeadr   r   r   r   Ú_prune_placedÂ   s
    r&   )r   Úreturnc                 C   sp   ddl }d| › �}|jtj| t d|¡dd�}|jdvrRtd|j› d	|j› �ƒ‚t ¡ t	| < |jrl| 
¡ S i S )
a3  DELETE /portfolio/events/orders/{id} -- the V2 cancel.

    NOT kalshi_trader.cancel_order, which still calls V1
    (/portfolio/orders/{id}) and has returned 410 deprecated_v1_order_endpoint
    since 2026-07-23. Placement was migrated to V2 then; cancellation was not,
    and nothing caught it because every order this project had ever sent was
    immediate-or-cancel and so never rested long enough to need cancelling. The
    first resting order ever placed (2026-08-15) could not be cancelled at all
    and was only cleaned up by its expiration_time.
    r   Nz/portfolio/events/orders/ZDELETEé   )ZheadersZtimeout)éÈ   éÌ   zKalshi DELETE v2 order -> ú: )ÚrequestsÚdeleteÚktZAPI_BASEZ_auth_headersZstatus_codeÚRuntimeErrorÚtextr!   Ú
_CANCELLEDÚjson)r   r,   ÚpathÚrr   r   r   Úcancel_order_v2Ì   s    
ÿ
r5   )Úflagr'   c                 C   s   t | ƒadS )z@Arm or disarm real order placement. Off until explicitly called.N)ÚboolÚ_LIVE)r6   r   r   r   Úset_liveè   s    r9   )r'   c                   C   s   t S )N)r8   r   r   r   r   Úis_liveî   s    r:   Ú c	                 C   s¶   t  ¡  }	t jjddd� t jdddd��x}
dd l}|j|
td�}|	rN| ¡  | 	t
j t
jj¡ ¡ | |||||ttƒ|p|d|t|ƒd d	… d
œ¡ W d   ƒ n1 s¨0    Y  d S )NT)ÚparentsÚexist_okÚar;   zutf-8)ÚnewlineÚencodingr   )Z
fieldnamesiX  r
   )Ú	ORDER_LOGÚexistsÚparentÚmkdirÚopenÚcsvZ
DictWriterÚ
LOG_FIELDSZwriteheaderZwriterowÚdtÚdatetimer   ÚtimezoneÚutcÚ	isoformatÚintr8   Ústr)r   r   r   r   r   r   r   r   r   ÚnewZfhrF   Úwr   r   r   Ú_logò   s    
ørQ   c                 C   sÎ   | rt | tƒstd| ›�ƒ‚t |ttfƒr4t |tƒrBtd|›�ƒ‚||krR|tk rftdt› d|›�ƒ‚|tkr‚td|› dt› �ƒ‚t |tƒsštd|›�ƒ‚t|  kr®t	ksÊn td|› d	t› d
t	› �ƒ‚d S )Nzbad ticker: zcount must be a number, got zcount must be >= z, got zcount z exceeds MAX_CONTRACTS=zprice_cents must be int, got zprice_cents z	 outside z..)
r   rN   Ú
ValueErrorrM   Úfloatr7   Ú	MIN_COUNTÚMAX_CONTRACTSÚMIN_PRICE_CENTSÚMAX_PRICE_CENTS)r   r   r   r   r   r   Ú	_validate  s     

ÿÿrX   c               
   C   sÔ   zt  ¡ pi  dg ¡pg } W n ty6   tdƒ Y S 0 d}| D ]Ž}zXt| d¡pr| d¡pr| d¡pr| d¡prdƒ}t| d	¡p–t| d
¡p�dƒd ƒ}W n ttfy¶   Y q@Y n0 ||tdd| ƒ 7 }q@|S )zÿDollars locked by orders currently resting, from the EXCHANGE.

    Read from the exchange rather than from our own state: our state is what we
    believe, and the whole point of a cap is to catch the case where belief and
    reality have diverged.
    Úordersr   ç        Úremaining_count_fpZremaining_countÚcount_fpr   r   Zyes_price_dollarsZ	yes_priceç      Y@ç      ð?)r.   Úget_open_ordersÚgetÚ	ExceptionrS   Ú	TypeErrorrR   Úmax)rY   ÚtotalÚoÚcÚpr   r   r   Úresting_collateral  s.    ÿþþþÿ
rh   c                   C   s:   zt t ¡ pi  dg ¡pg ƒW S  ty4   t Y S 0 d S )NrY   )r%   r.   r_   r`   ra   ÚMAX_CONCURRENT_ORDERSr   r   r   r   Úopen_order_count8  s    rj   )r   r'   c              
   C   sš   zt  ¡ pi  dg ¡pg }W n ty6   tdƒ Y S 0 |D ]X}| d¡| kr<z"t| d¡ph| d¡phdƒW   S  ttfy’   tdƒ Y   S 0 q<dS )a#  Net contracts held in `ticker`, from the EXCHANGE. Negative = short.

    Guards the case the quoter cannot see: a leg fills, our state misses it,
    and the next poll re-posts -- leaving us short twice what we intended. The
    exchange is the only authority on what we actually hold.
    Úmarket_positionsÚnanr   Úposition_fpZpositionr   rZ   )r.   Úget_positionsr`   ra   rS   rb   rR   )r   Úposrg   r   r   r   Úcurrent_position?  s    "rp   )r   r   r   Úclient_order_idÚttl_sr'   c                    s\  t ˆ||ƒ tt ¡ ƒt|ƒ }tj |tjj¡ ¡ }t	ƒ }|t
krttdˆd|||dd|› d�ƒ	 t|› dt
› �ƒ‚tˆƒ}||kr¤tdˆd|||dddƒ	 tdƒ‚zpt ¡ t ‰ ‡ fd	d
„t ¡ D ƒD ]}	t |	d¡ qÈ‡fdd
„t ¡ pîi  dg ¡púg D ƒ}
tdd„ |
D ƒƒ}W nX t�yl } z>tdˆd|||dddt|ƒj› �ƒ	 tdˆ› �ƒ‚W Y d}~n
d}~0 0 | | | td k�rÜtdˆd|||ddd|› d|› d|› dt› �ƒ	 td|› d|› d|› dt› dˆ› �
ƒ‚tƒ |d|d    }|tk�r4tdˆd|||ddd|d›�ƒ	 td|d›dtd›�ƒ‚ˆd|d›|d d›d |d!d!d"d#œ	}|�rf||d$< t�s”td%ˆd|||dd&t |¡ƒ	 d!|d'œS zt d(|¡}W nP t�yô } z6td)ˆd|||dd)t|ƒj› d*|› �ƒ	 ‚ W Y d}~n
d}~0 0 |�pþi  d+¡�p
i  d,¡�p"|�pi  d,¡}|�r:ˆ|d- ft |< t!ƒ  td.ˆd||||d&d/ƒ	 |S )0z±Rest a SELL-YES order that expires on its own.

    Returns the API response, or {'dry_run': True} when not armed. Raises on any
    cap breach BEFORE touching the network.
    ZrejectÚaskNZcapz orders already restingz resting orders >= cap zpositions unreadablez)cannot read positions -- refusing to postc                    s   g | ]\}}|ˆ k r|‘qS r   r   )r   Úkr   )Ú_cutr   r   r    ˆ  ó    z#place_maker_ask.<locals>.<listcomp>c                    s,   g | ]$}|  d ¡ˆ kr|  d¡tvr|‘qS )r   r   )r`   r1   ©r   re   )r   r   r   r    Š  s   þrY   c                 s   s*   | ]"}t | d ¡p| d¡pdƒV  qdS )r[   r\   r   N)rS   r`   rw   r   r   r   Ú	<genexpr>�  s   ÿÿÿz"place_maker_ask.<locals>.<genexpr>zopen orders unreadable: z/cannot read open orders -- refusing to post on g•Ö&è.>zshort z + resting z + z	 exceeds z plus z resting plus z would exceed MAX_CONTRACTS=z on r^   r]   zcollateral $z.2fz exceeds cap $z.4fÚgood_till_canceledTZtaker_at_cross)	r   r   r   ZpriceÚtime_in_forceÚexpiration_timeÚ	post_onlyZcancel_order_on_pauseZself_trade_prevention_typerq   Údry_runÚok)r}   Úbodyz/portfolio/events/ordersÚerrorr+   Úorderr   é   Zplacedr;   )"rX   rM   r!   rH   rI   ZfromtimestamprJ   rK   rL   rj   ri   rQ   r/   rp   Ú_CANCEL_MEMORY_Sr1   r#   r$   r.   r_   r`   Úsumra   ÚtypeÚ__name__rU   rh   ÚMAX_RESTING_COLLATERALr8   r2   ÚdumpsZ_postr"   r&   )r   r   r   rq   rr   ZexpiresZexp_isoZn_openZheldZ_oidZmineZresting_qtyÚeZcollr   r4   r   r   )ru   r   r   Úplace_maker_askY  s¢    
ÿÿ&ÿÿÿÿÿÿþþ
ÿÿ
÷
ÿ
ÿ.rŠ   c                  C   s@   zt  ¡ pi  dg ¡pg } W n ty0   Y dS 0 dd„ | D ƒS )a9  Events where the maker's subaccount holds a SHORT -- a book with at least
    one leg already sold. None if the positions cannot be read.

    From the EXCHANGE, not our state, for the same reason resting_collateral()
    is: this decides what survives a restart, and belief must not override the
    ledger.
    rk   Nc                 S   s>   h | ]6}t | d ¡pdƒdk rt| dd¡ƒ dd¡d ’qS )rm   r   g•Ö&è.¾r   r;   ú-r   )rS   r`   rN   Úrsplit)r   rg   r   r   r   Ú	<setcomp>Ð  s   ÿzheld_events.<locals>.<setcomp>)r.   rn   r`   ra   )Zpsr   r   r   Úheld_eventsÄ  s
    rŽ   )ÚreasonÚ	keep_heldr'   c           	      C   s  |r
t ƒ ntƒ }|du r6tdddddddddƒ	 tƒ }tƒ  dd„ t ¡ D ƒ}z@t ¡ pZi  dg ¡pfg D ]"}| d	¡rh| d
d¡||d	 < qhW nX t	yæ } z@tdddddddddt
|ƒ› dt|ƒj› d|› �ƒ	 W Y d}~n
d}~0 0 d}| ¡ D �]
\}}|�rD|�rDt|ƒ dd¡d |v �rDtd|dddd|d| › d�ƒ	 qôt�sltd|dddd|d| ƒ	 |d7 }qôz8t|ƒ td|dddd|d| ƒ	 t |d¡ |d7 }W qô t	�yþ } z@t |d¡ td|dddd|dt|ƒj› d|› �ƒ	 W Y d}~qôd}~0 0 qô|S )a}  Cancel everything we placed AND everything the exchange lists resting.

    The union matters. Listing alone misses orders the exchange has accepted but
    not yet published (see _PLACED); our own ids alone would miss anything left
    over from a previous process. Cancelling an already-dead order is harmless,
    so erring toward cancelling twice is the right direction.

    keep_held=True SPARES orders on any event we already hold a leg in -- the
    completing legs of a part-filled book (2026-09-29). Used on RESTART paths
    only, never on a stop. Those legs carry a long TTL precisely so they rest
    through an outage ("leaving it resting through an outage is the outcome we
    want", mm_maker_live's HELD_TTL note), but every restart cancelled them
    anyway: on 2026-09-28 the guard's restart at 00:06 pulled TURITA's TUR and
    TIE, the relaunch then failed on DNS, and the book sat one-legged for 8h.
    A completing leg is priced at the book's budget, so a fill completes it at
    a profit whoever wins -- keeping it is the safe side. If positions cannot
    be read, this FAILS CLOSED and cancels everything, as before.
    NÚ
cancel_allr;   r€   z>keep_held requested but positions unreadable -- cancelling allc                 S   s(   i | ] \}}|t |tƒr |d  n|“qS )r   r   r   r   r   r   Ú
<dictcomp>í  s   ÿzcancel_all.<locals>.<dictcomp>rY   r   r   z listing failed, falling back to z known ids: r+   r   r‹   r   Zkeptr~   z: completing leg of a held bookZdry_run_cancelZ	cancelledZcancel_fail)rŽ   ÚsetrQ   r&   r"   r#   r.   r_   r`   ra   r%   r…   r†   rN   rŒ   r8   r5   r$   )	r�   r�   ZkeepÚtargetsre   r‰   Únr   r   r   r   r   r‘   Ô  sX    ÿÿ
ÿÿÿ&ÿÿr‘   c                     s‚  t dt› d�ƒ tdu s J dƒ‚d‰ ‡ fdd„} | dd	„ d
ƒ | dd	„ dƒ | dd	„ dƒ | dd	„ dƒ | dd	„ dƒ | dd	„ dƒ | dd	„ dƒ | dd	„ dƒ | dd	„ dƒ tdddƒ t dƒ ˆ d7 ‰ td ddƒd! }|d" d#ksîJ |ƒ‚|d$ d%u �sJ |ƒ‚|d& d'k�sJ |ƒ‚|d( t ¡ k�s4J |ƒ‚|d( t ¡ t d) k�sVJ |ƒ‚t d*t› d+�ƒ ˆ d7 ‰ t d,ˆ › d-�ƒ d.S )/zDGuards only -- no network, no orders. Run before trusting this file.zLIVE = z  (must be False here)Fzmodule must not start armedr   c              
      sj   z
| ƒ  W nL t tfyV } z0td|› dt|ƒj› �ƒ ˆ d7 ‰ W Y d }~d S d }~0 0 t|› d�ƒ‚d S )Nz	  [pass] r+   r   z did NOT raise)rR   r/   Úprintr…   r†   ÚAssertionError)ÚfnZlabelr‰   ©r~   r   r   Úexpect_raise  s    
z_selftest.<locals>.expect_raisec                   S   s   t dddƒS )NÚTr   r   ©rX   r   r   r   r   Ú<lambda>  rv   z_selftest.<locals>.<lambda>zcount 0 rejectedc                   S   s   t dtd dƒS )Nr›   r   r   ©rX   rU   r   r   r   r   r�      rv   zcount above MAX_CONTRACTSc                   S   s   t dddƒS )Nr›   r   r   rœ   r   r   r   r   r�   !  rv   zprice 0 rejectedc                   S   s   t dddƒS )Nr›   r   éd   rœ   r   r   r   r   r�   "  rv   zprice 100 rejectedc                   S   s   t dtd dƒS )Nr›   r   r   rž   r   r   r   r   r�   &  rv   zfraction above cap rejectedc                   S   s   t dddƒS )Nr›   rZ   r   rœ   r   r   r   r   r�   '  rv   zcount below MIN_COUNT rejectedc                   S   s   t dddƒS )Nr›   Tr   rœ   r   r   r   r   r�   (  rv   zbool count rejectedc                   S   s   t dddƒS )Nr›   Ú1r   rœ   r   r   r   r   r�   )  rv   zstring count rejectedc                   S   s   t dddƒS )Nr;   r   r   rœ   r   r   r   r   r�   *  rv   zempty ticker rejectedr›   g      à?r   z&  [pass] fractional count 0.5 acceptedr   zKXTEST-1r   r   rs   r|   Trz   ry   r{   é   z7  [pass] dry-run body is a post-only expiring ASK (ttl zs)Ú
z' guard checks passed; nothing was sent.N)r–   r8   rX   rŠ   r!   ÚMAKER_TTL_S)rš   r   r   r™   r   Ú	_selftest  s6    
"ÿ
r¤   Ú__main__)N)r;   )r;   F)+Ú__doc__rI   rH   r2   r!   Úpathlibr   Útypingr   Zkalshi_traderr.   r£   rU   rT   rW   rV   rS   r‡   ri   rA   rG   r8   r"   r1   rƒ   r&   rN   Údictr5   r7   r9   r:   rQ   rX   rh   rM   rj   rp   rŠ   rŽ   r‘   r¤   r†   r   r   r   r   Ú<module>   sP   '# 

 ÿ
 þþk;,
