@@ -35,14 +35,19 @@ def compute_original_stats(trades: list[dict]) -> dict:
3535 buys = [t for t in trades if t .get ("side" ) == "BUY" ]
3636 sells = [t for t in trades if t .get ("side" ) == "SELL" ]
3737 redeems = [t for t in trades if t .get ("type" ) == "REDEEM" ]
38- total_invested = sum (t .get ("usdcSize" , 0.0 ) for t in buys )
38+ splits = [t for t in trades if t .get ("side" ) == "SPLIT" ]
39+ merges = [t for t in trades if t .get ("side" ) == "MERGE" ]
40+ # SPLIT spends $1 per share → outflow; MERGE returns $1 per share → inflow
41+ total_invested = (sum (t .get ("usdcSize" , 0.0 ) for t in buys )
42+ + sum (t .get ("usdcSize" , 0.0 ) for t in splits ))
3943 total_returned = (sum (t .get ("usdcSize" , 0.0 ) for t in sells )
40- + sum (t .get ("usdcSize" , 0.0 ) for t in redeems ))
44+ + sum (t .get ("usdcSize" , 0.0 ) for t in redeems )
45+ + sum (t .get ("usdcSize" , 0.0 ) for t in merges ))
4146
4247 pnl = total_returned - total_invested
4348 roi_pct = (pnl / total_invested * 100 ) if total_invested > 0 else 0.0
4449 return {"total_trades" : len (trades ), "buy_count" : len (buys ), "sell_count" : len (sells ),
45- "redeem_count" : len (redeems ),
50+ "redeem_count" : len (redeems ), "split_count" : len ( splits ), "merge_count" : len ( merges ),
4651 "total_invested" : total_invested , "total_returned" : total_returned ,
4752 "pnl" : pnl , "roi_pct" : roi_pct }
4853
@@ -52,7 +57,7 @@ def fetch(
5257 db : "Database" ,
5358 data_client : "PolymarketDataClient" ,
5459 * ,
55- max_trades : int = 10_000 ,
60+ max_trades : int = 100_000 ,
5661) -> dict :
5762 """Fetch a user's trades and redeems from API and store in DB.
5863
@@ -253,6 +258,77 @@ def replay(
253258 engine .db .update_cash (account .cash + replay_usd_sell )
254259 replayed .append (trade )
255260 replay_usd_final = replay_usd_sell
261+
262+ elif side == "SPLIT" :
263+ # SPLIT: spend $1 per share → get 1 YES + 1 NO share each
264+ split_cost = compute_replay_amount (original_usd , target .sizing )
265+ account = engine .get_account ()
266+ if account .cash < split_cost :
267+ skipped .append ({** orig , "reason" : f"Insufficient balance for SPLIT: need ${ split_cost :.2f} , have ${ account .cash :.2f} " })
268+ continue
269+ split_shares = split_cost # $1 per share pair
270+
271+ for split_outcome in ("yes" , "no" ):
272+ existing = engine .db .get_position (condition_id , split_outcome )
273+ if existing and existing .shares > 0 :
274+ total_shares = existing .shares + split_shares
275+ total_cost = existing .total_cost + split_cost
276+ new_avg = total_cost / total_shares
277+ else :
278+ total_shares = split_shares
279+ total_cost = split_cost
280+ new_avg = 1.0
281+ engine .db .upsert_position (
282+ market_condition_id = condition_id , market_slug = slug ,
283+ market_question = title , outcome = split_outcome ,
284+ shares = total_shares , avg_entry_price = new_avg , total_cost = total_cost )
285+
286+ engine .db .update_cash (account .cash - split_cost )
287+ trade = engine .db .insert_trade (
288+ market_condition_id = condition_id , market_slug = slug ,
289+ market_question = title , event_slug = event_slug ,
290+ outcome = "split" , side = "buy" , order_type = "fok" ,
291+ avg_price = 1.0 , amount_usd = split_cost ,
292+ shares = split_shares , fee_rate_bps = 0 , fee = 0.0 ,
293+ slippage = 0.0 , levels_filled = 1 , is_partial = False )
294+ replayed .append (trade )
295+ replay_usd_final = split_cost
296+
297+ elif side == "MERGE" :
298+ # MERGE: burn 1 YES + 1 NO share → get $1 back
299+ yes_pos = engine .db .get_position (condition_id , "yes" )
300+ no_pos = engine .db .get_position (condition_id , "no" )
301+ if not yes_pos or yes_pos .shares <= 0 or not no_pos or no_pos .shares <= 0 :
302+ skipped .append ({** orig , "reason" : "Need both YES and NO positions to merge" })
303+ continue
304+
305+ merge_shares = min (original_shares , yes_pos .shares , no_pos .shares )
306+ merge_usd = merge_shares # $1 per merged pair
307+
308+ for merge_outcome , pos in (("yes" , yes_pos ), ("no" , no_pos )):
309+ remaining_shares = pos .shares - merge_shares
310+ cost_of_merged = merge_shares * pos .avg_entry_price
311+ remaining_cost = pos .total_cost - cost_of_merged
312+ realized = (merge_usd / 2 ) - cost_of_merged # each side gets half the $1
313+ engine .db .upsert_position (
314+ market_condition_id = condition_id , market_slug = slug ,
315+ market_question = title , outcome = merge_outcome ,
316+ shares = remaining_shares , avg_entry_price = pos .avg_entry_price ,
317+ total_cost = max (remaining_cost , 0.0 ),
318+ realized_pnl = pos .realized_pnl + realized )
319+
320+ account = engine .get_account ()
321+ engine .db .update_cash (account .cash + merge_usd )
322+ trade = engine .db .insert_trade (
323+ market_condition_id = condition_id , market_slug = slug ,
324+ market_question = title , event_slug = event_slug ,
325+ outcome = "merge" , side = "sell" , order_type = "fok" ,
326+ avg_price = 1.0 , amount_usd = merge_usd ,
327+ shares = merge_shares , fee_rate_bps = 0 , fee = 0.0 ,
328+ slippage = 0.0 , levels_filled = 1 , is_partial = False )
329+ replayed .append (trade )
330+ replay_usd_final = merge_usd
331+
256332 else :
257333 skipped .append ({** orig , "reason" : f"Unknown side: { side } " })
258334 continue
0 commit comments