diff --git a/README.md b/README.md index 0258311c..73bf3e1c 100644 --- a/README.md +++ b/README.md @@ -290,21 +290,25 @@ runner is retired (GitHub throttled `*/5` to ~2h in practice — it copied 1 of location doesn't relocate *you* (trade live from Colombia months, paper from US months). -11. **All-Time / Conv P&L is the wallet's REALIZED track record** — the sum of - Polymarket's own `realizedPnl` per closed position over the wallet's full - history (`cache.closed_exits`, the incremental `/closed-positions` cache). - It's what a copier mirroring their buy/sell/hold actually banks, and it - equals the profile's **PM P&L** except for unrealized marks on positions - still open. This *replaced* the old hold-to-resolution reconstruction - (`won × entry × size`), which diverged from PM by up to 10× and even - flipped signs — four bugs: a 2,000-row pull cap (fixed: full history), - both-sides positions double-dropped by one-per-market dedup (fixed: each - asset is its own realized row), `initialValue = 0` mis-sizing (moot — - realized P&L needs no size), and corrupt near-epoch `res_t` (moot — - realized cash is timestamp-independent). Win % is now the share of closed - positions that *made money*, the mirror lens. (For wallets holding a large - losing open book, realized > PM because PM marks the open losers in; - that gap is unrealized, not an error.) +11. **All-Time / Conv P&L is the wallet's ANTI-SURVIVORSHIP realized track + record.** Every DECIDED position at its actual result: redeemed/sold closed + positions (Polymarket's own `realizedPnl`, `cache.closed_exits`) PLUS + resolved-but-unredeemed positions still sitting in `/positions` — bets that + already won/lost and the wallet never redeemed (mostly **abandoned losers** + at $0). `_open_split` in validate_timing pulls current positions and routes + those decided ones into the realized total (`cashPnl` = their outcome), + leaving only genuinely in-flight positions in the **Open P&L** column. This + is the whole point of the project surfacing: PM `/profit` **under-counts + abandoned losers**, unevenly — Coteykens' PM subtracts them (realized $66k − + $52k walked-away = $14k = PM), oliman2's does not ($181k − $161k = $20k + true, yet PM says $112k). So where our All-Time reads **lower** than PM, PM + is the survivorship-biased number and **ours is the truth** (oliman2 and + JuiceFarm look elite on redeemed-only P&L, ~$20k/$32k once the losers they + walked from are counted). This *replaced* the old hold-to-resolution + reconstruction (`won × entry × size`), which diverged by up to 10× and + flipped signs — four bugs killed with it: the 2,000-row pull cap, both-sides + double-drop, `initialValue = 0` mis-sizing, and corrupt near-epoch `res_t`. + Win % is the share of decided positions that *made money*. --- diff --git a/live/validate_timing.py b/live/validate_timing.py index 5e5e32be..b3ec5b68 100644 --- a/live/validate_timing.py +++ b/live/validate_timing.py @@ -93,24 +93,41 @@ def _pm_profit(w): return None -def _open_pnl(w): - """The wallet's OPEN (unrealized) P&L — sum of cashPnl over every current - position, full paging (the endpoint caps at 50/page). This is the exposure - sitting behind the realized track record: a wallet with a great All-Time - (realized) P&L but a large NEGATIVE open book is selling winners and - holding losers — the realized number flatters what you'd feel following - them live. PM /profit marks this in; our realized All-Time P&L doesn't, so - Open P&L is exactly the reconciling gap between them.""" - total = 0.0 +def _open_split(w): + """Split the wallet's current /positions into (open_pnl, resolved). + + open_pnl = unrealized P&L (cashPnl) over GENUINELY OPEN positions + (interior price) — real in-flight exposure. + resolved = decided-but-UNREDEEMED positions (curPrice pinned at 0 or 1): + bets that already won/lost and the wallet just never redeemed. + These are NOT open — they're realized outcomes hiding in the + positions endpoint (mostly abandoned LOSERS at $0). They + belong in the REALIZED track record (cashPnl = their decided + P&L: a loser is -cost). Leaving them in "open" is the exact + survivorship blind spot — PM /profit under-counts them, so a + wallet's realized looks better than the bets it walked away + from. Returned as [{realized_pnl, iv, ts}] to fold into + All-Time P&L + win/loss. + """ + open_pnl = 0.0 + resolved = [] for off in range(0, 100000, 50): pg = sm.get_json("/positions", {"user": w, "limit": 50, "offset": off, "sizeThreshold": 0}) if not pg: break - total += sum(p.get("cashPnl") or 0 for p in pg) + for p in pg: + cp = p.get("curPrice", 0) or 0 + cpnl = p.get("cashPnl") or 0 + if cp <= 0.001 or cp >= 0.999: # decided, just unredeemed + resolved.append({"realized_pnl": cpnl, + "iv": p.get("initialValue") or 0, + "ts": p.get("timestamp") or 0}) + else: + open_pnl += cpnl if len(pg) < 50: break - return round(total) + return round(open_pnl), resolved def _wp(cond, asset, won): @@ -177,7 +194,12 @@ def display_stats(w): scr_ += 1 return won_, lost_, scr_, round(pnl) - allpos = list(exits.values()) + # realized universe = redeemed/sold closed positions (exits, with realizedPnl) + # PLUS decided-but-unredeemed positions (resolved losers/winners still sitting + # in /positions). Folding the latter in is the anti-survivorship correction: + # a wallet's true realized record includes the bets it walked away from. + open_pnl, resolved_open = _open_split(w) + allpos = list(exits.values()) + resolved_open all_won, all_lost, all_scr, all_pnl = rtally(allpos) # conviction = the wallet's top-20%-by-stake positions (iv); conv30 = those # closed in the last 30d. Realized P&L over each set. @@ -199,7 +221,7 @@ def display_stats(w): "all_win": round(100 * all_won / (all_won + all_lost), 1) if (all_won + all_lost) else None, "all_won": all_won, "all_lost": all_lost, "all_ref": all_scr, "all_sold": 0, "all_pnl": all_pnl, - "open_pnl": _open_pnl(w), + "open_pnl": open_pnl, # genuinely in-flight only (resolved folded into realized) "pm_pnl": _pm_profit(w), "avg_bet": round(sum(e["iv"] for e in conv) / len(conv)) if conv else 0, "copy_pnl": 0, "held_pnl": 0, "held_won": 0, "held_lost": 0, "sold": 0,