P&L: fold abandoned losers into realized (anti-survivorship) — All-Time is the truth, not PM
Resolved-but-unredeemed positions (bets that lost, went to $0, never redeemed) were sitting in Open P&L, breaking the All-Time+Open=PM reconcile and — worse — hiding real losses. _open_split now routes decided positions (curPrice pinned 0/1) into the REALIZED total at their outcome, leaving only genuinely in-flight positions in Open P&L. This is the project's founding survivorship insight applied to P&L: PM /profit under-counts abandoned losers unevenly, so where our All-Time reads lower than PM, PM is biased and ours is honest. oliman2 $181k->$20k (walked from $161k of losers), JuiceFarm $380k->$32k; clean wallets unchanged (ewww1 $409k, KBO30 ~PM). Win% now counts abandoned losers too. Verified: Coteykens/imwalkinghere/KBO30 now ~PM; oliman2/JuiceFarm correctly far below their flattering PM. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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*.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+35
-13
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user