a
    ôÙmj)  ã                   @   sl   d Z ddlZddlZddlZddlmZ dZddddœZd	Zd
d„ Zdd„ Z	dd„ Z
edkrhe e
ƒ ¡ dS )a  Repair bet_history.json: one bet per signal, not one per venue.

THE BUG. xlow names ONE book as cheap and bets that book. But live_monitor
records `poly_stake` (the signal's named venue) and then, because
Polymarket.com is not tradeable from the US, ALSO writes `kalshi_stake` as a
fallback execution (live_monitor.py ~line 524). daily_signals.resolve_bets then
books P&L on each field independently:

    if bet.get('kalshi_stake') ... kalshi_pnl = ...
    if bet.get('poly_stake')   ... poly_pnl   = ...

so the SAME signal is paid out twice, at two slightly different prices. That is
what produced nightly-email rows like "+100.12 (K:+63.31 P:+36.81)".

Scope: 41 bets are dual-staked; 39 of them are pure paper (no order id at all),
2 have a real Kalshi order. Across those 41, the email reported -$343.47 when a
single bet would be roughly half.

THE REPAIR. Keep exactly ONE venue per bet: the CHEAPER ask, which is what xlow
actually calls and what research/forward_test_unique_book.py already does (that
is why the forward test at n=166 / -5.2% is NOT affected by this bug and needs
no correction). Ties go to Kalshi, which is the executable venue.

Nothing is deleted. The dropped venue's fields are preserved under a `dropped_`
prefix so the original record is fully recoverable and the decision auditable.

Bankrolls are then recomputed from scratch (all three start at $1,000, per
daily_signals.load_*_bankroll defaults) by replaying every resolved bet in date
order, so `*_bankroll_after` and the bankroll json files agree with the repaired
ledger. This matters beyond bookkeeping: _load_kalshi_bankroll feeds Kelly
sizing, so a wrong bankroll silently mis-sizes future bets.

Usage:  python research\fix_dual_stake_ledger.py [--apply]
        (default is a DRY RUN that reports and changes nothing)
é    N)ÚdatetimezE:\soccer\data\bet_history.jsonzE:\soccer\data\bankroll.jsonz#E:\soccer\data\kalshi_bankroll.jsonz!E:\soccer\data\poly_bankroll.json©Úb365ÚkalshiÚpolyg     @�@c              	   C   s(   z
t | ƒW S  ttfy"   Y d S 0 d S )N)ÚfloatÚ	TypeErrorÚ
ValueError)Úx© r   ú+E:\soccer\research\fix_dual_stake_ledger.pyÚ_f2   s    
r   c                 C   sJ   t |  d¡ƒt |  d¡ƒ }}|du r*dS |du r6dS ||d k rFdS dS )a>  Which single venue should this bet be booked on? Returns 'kalshi'|'poly'.

    The cheaper ASK is the book xlow named, matching fire_xlow's own choice.
    Ties -> kalshi, because Polymarket.com cannot actually be traded from the US
    so it is the wrong venue to attribute a fill to when the price is identical.
    Z
kalshi_askZpoly_askNr   r   g•Ö&è.>)r   Úget)ÚbetZkaZpar   r   r   Ú
pick_venue9   s    r   c                  C   sÌ  dt jv } t ttdd�¡}t|tƒr*|n
| d|¡}dd„ |D ƒ}t	t
|ƒ› dt
|ƒ› d�ƒ d	d	d
œ}|D ]p}t|ƒ}|dkr†dnd}||  d7  < dD ].}|› d|› �}	|	|v rž| |	¡|d|	› �< qž||d< d|d< qnt	d|d › d|d › d�ƒ t|dd„ d�}
tttdœ}|
D ]j}| d¡d u �r6�qdD ]J\}}}t| |¡ƒ}|d u �r`�q:t|| | dƒ||< || ||< �q:�qt	dƒ dD ]V}t tt| dd�¡d }t	d |d!›d"|d#›d$|| d#›d%|| | d&›d'�	ƒ �q–| �s t	d(ƒ d	S t ¡  d)¡}t tt› d*|› �¡ t ¡ D ] \}}t ||› d*|› �¡ �q,t	d+|› �ƒ tj|ttd,dd�dd-d.� t ¡ D ]>\}}tjt|| dƒt ¡  d/¡d0œt|d,dd�dd1� �q€t	d2ƒ d	S )3Nz--applyzutf-8)ÚencodingZbetsc                 S   s$   g | ]}|  d ¡r|  d¡r|‘qS )Zkalshi_stakeZ
poly_stake)r   )Ú.0Úrr   r   r   Ú
<listcomp>M   ó    zmain.<locals>.<listcomp>z bets total; z dual-staked
r   )r   r   r   r   é   )ZstakeZaskÚpnlÚbankroll_afterÚ_Zdropped_Zbooked_venuez
2026-08-01Zledger_repairedzkept kalshi leg: z   kept poly leg: Ú
c                 S   s.   t |  d¡p|  d¡pdƒt |  d¡p(dƒfS )NZresolved_atÚdateÚ )Ústrr   )r   r   r   r   Ú<lambda>_   s   ÿzmain.<locals>.<lambda>)Úkeyr   Zwon))r   r   r   )r   Z
kalshi_pnlZkalshi_bankroll_after)r   Zpoly_pnlZpoly_bankroll_afteré   z(recomputed bankrolls (from $1,000 each):Úbankrollz   Z7sú z>10,.2fz  ->  z   (z+,.2fú)z;
DRY RUN -- nothing written. Re-run with --apply to commit.z%Y%m%d_%H%M%Sz.bak_z"
backups written with suffix .bak_ÚwF)ÚindentZensure_asciiz%Y-%m-%d %H:%M)r!   Úupdated)r%   z6bet_history.json and all three bankroll files updated.)ÚsysÚargvÚjsonÚloadÚopenÚHISTÚ
isinstanceÚlistr   ÚprintÚlenr   ÚpopÚsortedÚSTARTr   ÚroundÚ	BANKROLLSr   ZnowÚstrftimeÚshutilZcopy2ÚitemsÚdump)ZapplyZhistZrowsZdualZkeptr   ZkeepZdropÚfÚsrcÚorderÚrunr   ZvenZpfZbfÚvZcurZstampÚpathr   r   r   ÚmainH   sf    



 ÿÿþ
r@   Ú__main__)Ú__doc__r)   r7   r'   r   r,   r5   r3   r   r   r@   Ú__name__Úexitr   r   r   r   Ú<module>   s   #ý?