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ZdZ	dZ
dZdZej ej e¡¡Zej ed¡Zd	Zd
Zddd„Zddd„Zdd„ Zddd„Zdd„ Zedkr¼e eƒ ¡ dS )a˜  Exact P&L for Polymarket soccer makers, from Polygon logs. (read-only)

WHY ON-CHAIN. The REST route cannot do this. /activity caps at offset ~5400 and
40 of 42 real makers exceed it, and a truncated ledger biases P&L UPWARD without
limit -- a SELL or REDEEM whose purchase falls outside the window has no cost
basis and reads as pure profit (it produced a fake $8.8M row). On-chain there is
no such cap: OrderFilled indexes `maker`, so one wallet's entire fill history is
directly queryable.

MAKER-FIRST, NOT MARKET-FIRST. `assetId` is NOT indexed, and the exchange emits
~46k OrderFilled per 1000 blocks (~2M/day) across all of Polymarket, so scanning
a market's block range and filtering locally is impossible on a public RPC.
Queries must be keyed on the maker address, then filtered to soccer tokens.

DECODING (verified against the API to the cent on
0xfb101b79...fca7e): topics = [sig, orderHash, maker, taker]; data words =
[makerAssetId, takerAssetId, makerAmountFilled, takerAmountFilled, fee, ...].
assetId 0 is USDC, so makerAssetId==0 means the MAKER PAID USDC (bought the
token) and takerAssetId==0 means the maker SOLD it. Amounts are 1e6-scaled.

P&L = net USDC from fills + settlement value of the net token position, where
settlement comes from gamma outcomePrices. A soccer market's whole lifecycle
fits inside the window, so every position has its cost basis -- which is the
property the REST route could not provide.

THE RELIABILITY FLAG. SPLIT/MERGE mint or burn a Yes+No pair for $1 without any
fill, so a wallet that uses them has cash flow this script cannot see. Those
counts are read from /activity and reported as `split_merge`; a row with a large
value is NOT trustworthy and is flagged rather than silently ranked.

STILL NOT ANSWERABLE, whatever we do here: QUOTES. Polymarket's CLOB is
off-chain, so resting orders are public nowhere. Everything below is fills, so
"legs" means legs FILLED, never legs quoted.
é    Nz&https://polygon-bor-rpc.publicnode.comZ*0xe111180000d2663c0091e4f400237545b87b996bZB0xd543adfd945773f1a62f74f0ee55a5e3b9b1a28262980ba90b1a89f2ea84d8eez https://gamma-api.polymarket.comzhttps://data-api.polymarket.comzpm_onchain_pnl.csvi'  iÀ¨  é   c              	   C   st   t |ƒD ]f}z<tjtdd| |dœdd�}| ¡ }d|v rF|d W   S W n tyZ   Y n0 t d|d  ¡ qd S )Nz2.0é   )ZjsonrpcÚidÚmethodÚparamsé-   )ÚjsonÚtimeoutÚresultç      ð?)ÚrangeÚrequestsZpostÚRPCr   Ú	ExceptionÚtimeÚsleep)r   r   ÚtriesÚiÚrÚj© r   úresearch/pm_onchain_pnl.pyÚrpc7   s    
ÿþr   é   c              	   C   sv   t |ƒD ]h}z>tj| |dd�}|jdkr6| ¡ W   S |jdkrHW  d S W n ty\   Y n0 t d|d  ¡ qd S )Né   )r   r	   éÈ   é�  r   r   )r   r   ÚgetZstatus_coder   r   r   r   )Zurlr   r   r   r   r   r   r   ÚhttpF   s    

r   c                  C   s$  i } dD �]}dD �]}t t› d�d|d|dddœƒ}|s> q|D ]Ø}| d	¡pRg }t|ƒd
ksBd| d¡pnd ¡ vrxqB|D ]œ}z6t | d¡p’d¡}dd„ t | d¡p¬d¡D ƒ}W n tyÌ   Y q|Y n0 t|ƒD ]@\}	}
| d¡| d¡|	t|ƒk �r||	 ndf| t	t
|
ƒƒ< qÖq|qBqq| S )z9token_id -> (match_slug, leg_question, settlement_price).)ÚtrueÚfalse)r   éd   r   i,  ú/eventsr!   ZsoccerZvolumer    )ÚlimitÚoffsetZtag_slugÚclosedÚorderZ	ascendingÚmarketsr   z vsÚtitleÚ ZclobTokenIdsz[]c                 S   s   g | ]}t |ƒ‘qS r   ©Úfloat©Ú.0Úxr   r   r   Ú
<listcomp>e   ó    z!soccer_tokens.<locals>.<listcomp>ZoutcomePricesÚslugZquestionN)r   ÚGAMMAr   ÚlenÚlowerr   Úloadsr   Ú	enumerateÚstrÚint)Útokr%   ZoffZevsÚeZmsÚmZidsZprr   Útidr   r   r   Úsoccer_tokensT   s2    


ÿÿ""
ÿr=   c                    s>  d| dd…   ¡  }g }|}||k �r:t|t |ƒ}tdt|ƒt|ƒttd|gdœgƒ}|p^g D ]È}	|	d dd… ‰ ‡ fdd„td	tˆ ƒd
ƒD ƒ}
t|
ƒdk r q`t	t
|
d	 dƒƒt	t
|
d dƒƒ }}t
|
d dƒd t
|
d dƒd  }}|dk�r| ||
 | f¡ q`|dkr`| || |
 f¡ q`|}t d¡ q|S )z;Every OrderFilled where this wallet was the MAKER, decoded.Z0x000000000000000000000000é   NZeth_getLogs)Z	fromBlockZtoBlockZaddressZtopicsÚdatac                    s   g | ]}ˆ ||d  … ‘qS )é@   r   )r-   r   ©Údr   r   r/   z   r0   zmaker_fills.<locals>.<listcomp>r   r@   r   é   r   g    €„.Ar   Ú0gš™™™™™©?)r4   ÚminÚCHUNKr   ÚhexÚEXCHANGEÚORDER_FILLEDr   r3   r7   r8   Úappendr   r   )ÚwalletZ
from_blockZto_blockÚlogZtopicÚoutÚbÚhiÚresÚlÚwZmk_assetZtk_assetZmk_amtZtk_amtr   rA   r   Úmaker_fillsn   s.    
þ
&&
rS   c            -      C   s  t tjƒdkrttjd ƒnd} t tjƒdkr<ttjd ƒnd}ttdg ƒdƒ}tƒ }tdt |ƒ› �ƒ g }d}td	d
„ | 	¡ D ƒƒd d… D ]Æ}t
t› d�d|iƒ}|s¬qŽ|D ]”}| d¡pÀg D ]€}	t
t› d�|	 d¡dddœƒ}
|
rþ| t|
d d ƒ¡ t
t› d�|	 d¡ddddœƒ}|�r:| t|d d ƒ¡ |d7 }qÂq°|dkrŽ �qVqŽ|�shtdƒ dS tt ¡ ƒ}t|ƒt|ƒ }}|t|| |d  d ƒ }|ttd|| |d  ƒd ƒ }tdt dt |¡¡t dt |¡¡f ƒ td|||| t f ƒ |}tt ttj td¡dd�¡ƒ}dd„ |D ƒ}|jd d!„ d"� |d | … }td#t |ƒ› d$�ƒ g }t|dƒD �]ä\}}|d% }t|||ƒ}d&}t  t¡}d}t  t!¡}|D ]V\}}}||v�rÐ�q¸|d7 }||7 }||  |7  < || \}} }!||  "| ¡ �q¸d&}"d}#| #¡ D ]6\}} || d }$|$d u �rH|#d7 }#n|"| |$ 7 }"�q t
t› d'�|d(d)œƒ�prg }%t$d*d+„ |%D ƒƒ}&d,d„ | 	¡ D ƒ}'| |t%||" dƒ|t |ƒ|'�rÎt%t$|'ƒt |'ƒ dƒnd|'�röt%t$d-d+„ |'D ƒƒt |'ƒ dƒndt |ƒ|#|&t|&dk�o|#dkƒd.œ
¡ td/|t |ƒ|d d… |t |ƒ||" |d0 d1 �rTd2nd3f ƒ �qz|jd4d!„ d"� tt&d5d2dd6��<}(tj'|(t|d  (¡ ƒd7�})|) )¡  |) *|¡ W d   ƒ n1 �sÆ0    Y  td8t |ƒ› d9t&› �ƒ d:d„ |D ƒ}*td;t |*ƒ ƒ |*d d<… D ]:}+td=|+d% |+d> |+d? |+d@ |+dA dB|+dC  f ƒ �q|*�rdDd„ |*D ƒ}$tdEt |$ƒt |*ƒdBt |$ƒ t |*ƒ f ƒ |$�rÒtdFt$dGd+„ |$D ƒƒt |$ƒ dBt$dHd+„ |$D ƒƒ t |$ƒ f ƒ dId„ |*D ƒ},|,�rtdJt$dKd+„ |,D ƒƒt |,ƒ  ƒ dS )LNr   é   r>   g       @Zeth_blockNumberrC   zsoccer tokens mapped: r   c                 S   s   h | ]}|d  ’qS )r   r   ©r-   Úvr   r   r   Ú	<setcomp>™   r0   zmain.<locals>.<setcomp>é   r"   r1   r'   z/tradesZconditionIdr    )Úmarketr#   Ú	takerOnlyZ	timestampr   )rY   r#   r$   rZ   z!could not date the soccer marketsi€Q zsoccer activity spans %s .. %sz%Y-%m-%dz blocks %d..%d  (%d calls/wallet)zpm_maker_study.csvzutf-8)Úencodingc                 S   s    g | ]}t |d  ƒdkr|‘qS )Zmaker_shareg333333ã?r*   ©r-   r   r   r   r   r/   ¸   r0   zmain.<locals>.<listcomp>c                 S   s   t | d ƒ S )NÚfills)r8   ©r   r   r   r   Ú<lambda>¹   r0   zmain.<locals>.<lambda>)Úkeyzcandidate makers: Ú
rK   g        z	/activityiô  )Úuserr#   c                 s   s    | ]}|  d ¡dv rdV  qdS )Útype)ZSPLITZMERGEr   N)r   )r-   Úar   r   r   Ú	<genexpr>Ö   r0   zmain.<locals>.<genexpr>c                 S   s   g | ]}t |ƒ‘qS r   )r3   rU   r   r   r   r/   ×   r0   c                 s   s   | ]}|d krdV  qdS )r   r   Nr   r,   r   r   r   re   Þ   r0   )
rK   Ú
pnl_soccerÚsoccer_fillsÚmatchesÚmean_legs_filledÚ
all3_shareZtotal_fills_windowZunresolved_tokensZsplit_merge_recentÚreliablez8  %2d/%d %s soccer_fills=%-6d matches=%-4d pnl=%12.2f %séÿÿÿÿrk   r)   z	(flagged)c                 S   s
   | d  S )Nrf   r   r^   r   r   r   r_   è   r0   rR   )Únewliner[   )Z
fieldnamesz
wrote z -> c                 S   s$   g | ]}|d  r|d dkr|‘qS )rk   rg   rT   r   r\   r   r   r   r/   í   r0   zF
RELIABLE ROWS (no split/merge, all positions settled, >=20 fills): %dé   z@  %-44s pnl=%11.2f fills=%-6d matches=%-4d legs=%.2f all3=%.0f%%rf   rg   rh   ri   r!   rj   c                 S   s   g | ]}|d  dkr|‘qS ©rf   r   r   r\   r   r   r   r/   ô   r0   z 
  profitable: %d of %d (%.0f%%)z7  profitable makers: mean legs filled %.2f, all3 %.0f%%c                 s   s   | ]}|d  V  qdS ©ri   Nr   r\   r   r   r   re   ø   r0   c                 s   s   | ]}|d  V  qdS )rj   Nr   r\   r   r   r   re   ù   r0   c                 S   s   g | ]}|d  dkr|‘qS ro   r   r\   r   r   r   r/   ú   r0   z*  losing makers    : mean legs filled %.2fc                 s   s   | ]}|d  V  qdS rp   r   r\   r   r   r   re   ý   r0   )+r3   ÚsysÚargvr8   r+   r   r=   ÚprintÚlistÚvaluesr   r2   r   ÚDATArJ   r   ÚmaxrE   ÚstrftimeÚgmtimerF   ÚcsvZ
DictReaderÚopenÚosÚpathÚjoinÚHEREÚsortr6   rS   ÚcollectionsÚdefaultdictÚsetÚaddÚitemsÚsumÚroundÚOUTZ
DictWriterÚkeysZwriteheaderZ	writerows)-ZtopnZpad_dÚheadr9   ZtsÚseenZtid_slugrB   r:   r;   ZtrZtr2ZnowZhi_tsZlo_tsZfrmÚtoÚsrcZcandsZrowsr   ÚcrR   r]   ZcashÚposZn_soccerZlegsr<   ZdqZdcashr1   ÚqÚ_ZsettleZ
unresolvedÚpZactZsmZnlegsZfhZwtrZrelr   rQ   r   r   r   Úmainˆ   sè      "ÿþ"ÿÿ





(ö ÿÿ
2ÿÿ
*ÿÿÿr“   Ú__main__)r   )r   )N)Ú__doc__r�   rz   r   r|   rq   r   r   r   rH   rI   r2   rv   r}   ÚdirnameÚabspathÚ__file__r   r~   rˆ   rF   ZBLOCKS_PER_DAYr   r   r=   rS   r“   Ú__name__Úexitr   r   r   r   Ú<module>   s.   "


y