a
    ±ß�jK  ã                   @   sš  d Z ddlZddlZddlZddlmZ ddlZddlm	Z	m
Z
 dZdZdZdZd	ZzTddlZddlZej dej ej ej ej e¡¡¡d
¡¡ ddlmZmZ W n eyÂ   dZdZY n0 d2eeeeeee e!dœdd„Z"eeedœdd„Z#e$dœdd„Z%eee!dœdd„Z&d3e e$dœdd „Z'ddl(Z(ddlZdd!lmZm)Z) dd"l*m+Z+ d#Z,d$Z-d%Z.d&Z/d'd(„ Z0d)d*„ Z1d+d,„ Z2d-d.„ Z3e!ee! d/œd0d1„Z4dS )4uh  
Polymarket US API client â€” order placement (Phase 2).

Mirrors kalshi_trader.py's shape: hard safety caps enforced BEFORE any HTTP
request, minimal surface (place + cancel + read fills). Uses poly_us_auth's
Ed25519-signed request helpers.

Order model (docs.polymarket.us/concepts/orders): price is always the YES
side (0 < price < 1, tick 0.01); quantity is in CONTRACTS, not dollars â€”
notional = quantity * price. To bet a side's "YES" (matches xlow's bet
convention exactly): marketSlug = that side's specific market slug (per-team
market, from the event's `markets[].slug`), intent = ORDER_INTENT_BUY_LONG.
é    N)ÚOptional)Ú_auth_headersÚAPI_BASEg®Gáz®ï?g{®Gáz„?é2   gš™™™™™É?ç      ð?Zresearch)ÚXLOW_PRICE_FLOORÚBANDgš™™™™™¹?)gš™™™™™©?g¸…ëQ¸¾?ÚORDER_INTENT_BUY_LONGÚTIME_IN_FORCE_DAYF)Úmarket_slugÚpriceÚquantityÚintentÚtifÚ	post_onlyÚttl_secsÚreturnc                 C   sB  t |  krtks0n tdt › dt› d|› �ƒ‚d|  k rDtksZn tdt› d|› �ƒ‚|| }|tkr‚td|d›dtd›�ƒ‚| d	|d›d
dœt|dƒ||dœ}|r°d|d< |rî|dkrîtj tj	j
¡tj|d� }	d|d< |	 d¡|d< tddƒ}
d|
d< tjtd |
|dd�}|jdv�r:td|j› d|j› �ƒ‚| ¡ S )aö  
    Place a LIMIT order. Hard caps enforced BEFORE any HTTP request.
    price: 0 < price < 1 (YES-side price)
    quantity: contracts (notional = quantity * price)

    post_only -- sends `participateDontInitiate`, which the venue documents as
        "order must rest on the book prior to matching (maker only). Order will
        be rejected if it would immediately match." REQUIRED for the maker: the
        entire economic case is receiving the 0.0125*p(1-p) maker rebate instead
        of paying the 0.06*p(1-p) taker fee, a ~5x swing, and a single crossing
        order flips the sign on that book. Kalshi's maker has always run
        post_only for the same reason.

    ttl_secs -- when >0, sends TIME_IN_FORCE_GOOD_TILL_DATE with `goodTillTime`
        that many seconds out, so the exchange expires the order by itself.
        This is the safety net the Kalshi maker relies on (MAKER_TTL_S=90): a
        crash or a power cut cannot leave anything resting. That failure is not
        hypothetical here -- this machine sat off for 2.5 days unnoticed on
        2026-08-10 and had a 15-hour silent outage on 2026-08-18. Default DAY
        orders would rest until the session closes.

    NOTE both fields are documented but UNVERIFIED on this account. Verify with
    research/polyus_capability_probe.py before trusting them. Docs and reality
    have diverged here before: cancel_order below called an endpoint that simply
    did not exist, and nothing noticed for the whole period it was auto-trading.
    zprice must be in [ú,z], got r   zquantity must be in (0,zdollar exposure $ú.2fz exceeds cap $ZORDER_TYPE_LIMITZUSD)ÚvalueZcurrencyé   )Ú
marketSlugÚtyper   r   r   r   TZparticipateDontInitiate)ÚsecondsZTIME_IN_FORCE_GOOD_TILL_DATEr   z%Y-%m-%dT%H:%M:%SZZgoodTillTimeÚPOSTz
/v1/ordersúapplication/jsonúContent-Typeé   ©ÚheadersÚjsonÚtimeout)éÈ   éÉ   zPOST /v1/orders -> ú: )Ú	MIN_PRICEÚ	MAX_PRICEÚ
ValueErrorÚMAX_QUANTITY_PER_ORDERÚMAX_DOLLARS_PER_ORDERÚroundÚ_dtÚdatetimeÚnowÚtimezoneÚutcZ	timedeltaÚstrftimer   ÚrequestsÚpostr   Ústatus_codeÚRuntimeErrorÚtextr    )r   r   r   r   r   r   r   Zdollar_exposureÚbodyZexpiresr   Úr© r8   úmain/poly_us_trader.pyÚplace_limit_order0   s4    ú
r:   )Údollarsr   r   c                 C   sN   t |  krtks$n td|› �ƒ‚t| tƒ} t | | d ¡d }t|tƒS )aŽ  Contracts to hit a target dollar notional at this price, capped, floored at 1 tick worth.
    Rounds DOWN (not to-nearest) -- round-to-nearest could push cost slightly
    OVER the dollar cap (e.g. 1.00/0.18=5.5555 -> round()=5.56 -> 5.56*0.18=
    $1.0008 > $1.00 cap), tripping place_limit_order's own safety check and
    rejecting the order entirely. Found 2026-07-24 on a real $1.00-cap order.zprice out of range: éd   )r%   r&   r'   Úminr)   ÚmathZfloorr(   )r;   r   Úqtyr8   r8   r9   Úsize_for_dollarso   s
    
r@   )r   c                  C   sd   t ddƒ} tjtd | dd�}|jdkr@td|j› d|j› �ƒ‚| ¡ pJi }| d¡pb| d	¡pbg S )
zGEvery resting order on the account. GET /v1/orders is 501; /open works.ÚGETz/v1/orders/openr   )r   r!   r"   zGET /v1/orders/open -> r$   ÚordersÚdata©r   r1   Úgetr   r3   r4   r5   r    )r   r7   Údr8   r8   r9   Úopen_orders|   s    

rG   )Úorder_idr   r   c                    s¨   d}dˆ |dœgi}t d|ƒ}d|d< tjt| ||dd�}|jd	krdtd
ˆ › d|j› d|j› �ƒ‚|jrr| ¡ ni }‡ fdd„tƒ D ƒ}|r¤tdˆ › d|› d�ƒ‚|S )aX  Cancel a resting order, then VERIFY it is actually gone.

    FIXED 2026-07-29. The previous implementation called
    `DELETE /v1/orders/{id}`, which does not exist and returns 404 -- so this
    account had NO working cancel for the entire period it was auto-trading.
    Nothing surfaced it because nothing had ever needed to cancel until a
    capability probe left a resting sell that could not be pulled.

    Uses the BATCHED endpoint rather than the documented single-order path
    (`POST /v1/order/{id}/cancel`) because batched is the one we have actually
    observed working end-to-end here: it returned 200 and the order verifiably
    left the book. Prefer the path with evidence over the path with docs.

    The response CANNOT be trusted on its own -- Polymarket's own docs say
    `canceledOrderIds` is "an echo of the request, not a confirmation", and the
    exchange may silently ignore unknown ids. So this re-reads open orders and
    raises if the order is still resting. A cancel that reports success while
    leaving an order live is the worst possible failure for a market maker.
    z/v1/orders/batched/cancelrB   )ZorderIdr   r   r   r   r   r   r"   zcancel ú -> r$   c                    s   g | ]}|  d ¡ˆ kr|‘qS )Úid©rE   )Ú.0Úo©rH   r8   r9   Ú
<listcomp>£   ó    z cancel_order.<locals>.<listcomp>zCANCEL NOT EFFECTIVE: z( is still resting after a 200 response (z). Cancel it manually.)	r   r1   r2   r   r3   r4   r5   r    rG   )rH   r   Úpathr6   r   r7   ÚrespZstillr8   rN   r9   Úcancel_order†   s     

ÿÿrS   r<   )Úlimitr   c                 C   sf   d}t d|ƒ}tjt| |d| dœdd�}|jdkrRtd|› d	|j› d
|j› �ƒ‚| ¡ p\i  dg ¡S )a  Our fills. FIXED 2026-07-29: `GET /v1/trades` does not exist (404) --
    trades live on the portfolio activities feed. `isAggressor` is the
    taker flag, which is what a maker strategy needs to confirm it was
    passive (and therefore earned the rebate rather than paying the fee).z/v1/portfolio/activitiesrA   ZACTIVITY_TYPE_TRADE)ÚtypesrT   r   )r   Úparamsr!   r"   zGET rI   r$   Z
activitiesrD   )rT   rQ   r   r7   r8   r8   r9   Ú
get_trades«   s    
þ
rW   )r,   r.   )ÚPathTz%E:\soccer\data\AUTO_TRADE_POLYUS_KILLz)E:\soccer\data\auto_trade_polyus_log.jsonz+E:\soccer\logs\auto_trade_polyus_errors.logc                	   C   s4   zt  ttƒ ¡ ¡W S  ttfy.   g  Y S 0 d S )N)r    ÚloadsrX   ÚAUTO_TRADE_LOGÚ	read_textÚFileNotFoundErrorr'   r8   r8   r8   r9   Ú_load_auto_logÖ   s    r]   c                 C   s   t tƒ tj| dd�¡ d S )Nr   )Úindent)rX   rZ   Z
write_textr    Údumps)Úlogr8   r8   r9   Ú_save_auto_logÝ   s    ra   c                    s   t ‡ fdd„tƒ D ƒƒS )Nc                 3   s   | ]}|  d ¡ˆ kV  qdS )Úclient_order_idNrK   )rL   Úe©rb   r8   r9   Ú	<genexpr>â   rP   z"_already_placed.<locals>.<genexpr>)Úanyr]   rd   r8   rd   r9   Ú_already_placedá   s    rg   c                 C   s’   zlt tƒjjddd� ttddd��6}| t tj	¡j
dd�› d| › d	�¡ W d   ƒ n1 s`0    Y  W n ty~   Y n0 td
| › �ƒ d S )NT)ÚparentsÚexist_okÚazutf-8)Úencodingr   ©Ztimespecz  Ú
z  [AUTO-POLYUS] ERROR: )rX   ÚAUTO_TRADE_ERR_LOGÚparentÚmkdirÚopenÚwriter,   r-   r.   r/   Ú	isoformatÚ	ExceptionÚprint)ÚmsgÚfr8   r8   r9   Ú
_log_errorå   s    Hrx   )Úsignalr   c                 C   sX  ddl }tsdS tj t¡r(tdƒ dS |  d¡}zddlm	}m
} W n6 ty| } ztd|› d�ƒ W Y d}~dS d}~0 0 |dur¢||vr¢td|› d	�ƒ dS ||v r¾td|› d
�ƒ dS zddlm} || ƒsÚW dS W n tyî   Y n0 d|  dd¡ ¡ v �r
dnd}|  d¡|  d¡ }}	d|  d¡› d|› �}
t|
ƒ�rXtd|
› d�ƒ dS | |||	|¡}|�srdS | |d ¡}|�r’| d¡�s–dS |d }t|  k�r¶tk�sÖn td|d › d|› d�ƒ dS |tk �rtd|d › d|d›dtd›d�ƒ dS d}z8t|  d¡�pdƒ}d|  k �r:dk �rBn n|}W n ttf�y\   Y n0 |du �rÞz\t|  d ¡�pzdƒ}t|  d!¡�pŽdƒd" }|d#|  }d|  k �rºdk �rÂn n|}W n ttf�yÜ   Y n0 |du�r>t|| ƒ| tk�r>td|d › d$|d›d%|d›d&t|| ƒ| d' d(›d)�	ƒ dS tt|ƒ}|dk�rVdS t|| d*ƒ}zt|d ||ƒ}W nL t�yÄ } z2td+|d › d,|› d-|› d.|› �ƒ W Y d}~dS d}~0 0 t  t!j"¡j#d/d0�| d1¡|
|d ||||  d¡||	||  d¡d2œ}t$ƒ }| %|¡ t&|ƒ td3|d › d,|› d-|› d4|d›d5| d1¡› �
ƒ |S )6a   
    Auto-place a Polymarket US limit-at-ask order for an xlow signal.
    Returns dict with order details on success, None on skip/failure.
    Mirrors daily_signals.place_auto_order's gate order:
      1. AUTO_TRADE_ENABLED  2. kill-switch  3. league WHITELIST then exclude
      4. find matching market  5. idempotent client_order_id
      6. live BBO fetch + drift check  7. sanity  8. place  9. log + email
    r   Nz+  [AUTO-POLYUS] kill-switch engaged -> skipÚdiv)ÚAUTO_TRADE_LEAGUESÚAUTO_TRADE_EXCLUDE_LEAGUESz*  [AUTO-POLYUS] cannot read league scope (z) -> refusing to tradez  [AUTO-POLYUS] z( not in AUTO_TRADE_LEAGUES -> paper onlyz( excluded from real orders -> paper only)Úpin_fair_okZAWAYÚbetÚ ÚawayÚhomezpolyus-Ú
fixture_idú-z$ already in log -> skip (idempotent)ZslugZbestAskz
 live_ask=z out of range -> skipz ask=z.3fz below the f10 floor r   z -> paper onlyZentry_p_midé   Zpin_fairZclv_polyg      Y@r   z drifted: signal z	 -> live z (r<   z.0fz
%) -> skipé   zplace_limit_order failed ú ú@r$   r   rl   rJ   )Z	placed_atrH   rb   r   r   r   Údollar_committedr‚   r�   r€   rz   r~   z  [AUTO-POLYUS] PLACED z ($z) id=)'Zpoly_us_pricesÚAUTO_TRADE_ENABLEDÚosrQ   ÚexistsÚAUTO_TRADE_KILL_FILEru   rE   Zdaily_signalsr{   r|   rt   r}   ÚImportErrorÚupperrg   Zfind_marketZget_bbor%   r&   r   ÚfloatÚ	TypeErrorr'   ÚabsÚMAX_PRICE_DRIFTr@   r)   r*   r:   rx   r,   r-   r.   r/   rs   r]   Úappendra   )ry   Zpricesrz   r{   r|   rc   r}   Zsider�   r€   rb   ZmarketZbboZlive_askZ_refZ_mZ_f2Z_c2Z_p2r?   rˆ   rR   Úrecordr`   r8   r8   r9   Úplace_auto_orderï   s¸    	



ÿ

 ÿÿ


$÷
2r•   )r	   r
   Fr   )r<   )5Ú__doc__rŠ   r>   r,   r+   Útypingr   r1   Zpoly_us_authr   r   r&   r%   r(   r’   r)   ÚsysÚ_sysÚ_osrQ   ÚinsertÚjoinÚdirnameÚabspathÚ__file__Zforward_test_unique_bookr   r   rt   Ústrr�   ÚboolÚintÚdictr:   r@   ÚlistrG   rS   rW   r    r.   ÚpathlibrX   r‰   rŒ   rZ   rn   r]   ra   rg   rx   r•   r8   r8   r8   r9   Ú<module>   sb   
ÿÿ
    üü?
%
