exits: FULL history, no window, no cap (holistic data > speed)

Per the data-completeness call: closed_exits pulls each wallet's ENTIRE
closed-position history, not a 180d window. smart_money.closed_exits returns
(exits, reached_end) — reached_end True only when the pull hits the true
start of history (empty/short page), so the cache's  flag tells a
finished backfill from an interrupted one and a killed deep pull re-completes
instead of falsely reporting done. One-time deep cost, amortized by the
incremental cache (later runs page only new closes). Verified: imwalkinghere
full 65d lifetime (879 exits, its real age not a cap), complete=True, 2nd
call 0.00s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jaxperro
2026-07-07 11:49:17 -04:00
parent 6d59b354ee
commit 2b33ec5683
2 changed files with 54 additions and 50 deletions
+31 -23
View File
@@ -107,35 +107,38 @@ def leaderboard_candidates(pool):
return ranked[:pool]
def closed_exits(wallet, since_ts=0, max_rows=20000, newest_bound=0):
"""{asset: {ts, exit_p, p, iv, cond, title, outcome}} for the wallet's
FULLY-CLOSED positions, newest first. `ts` is the close (sell/redeem)
timestamp; the exit price is reconstructed from realized P&L over shares
bought (exit_p = avgPrice + realizedPnl/totalBought — exact for a full
single-price exit, share-weighted otherwise).
def closed_exits(wallet, since_ts=0, max_rows=200000, newest_bound=0):
"""(exits, reached_end) — the wallet's FULLY-CLOSED positions.
exits = {asset: {ts, exit_p, p, iv, cond, title, outcome}}, newest first.
`ts` is the close (sell/redeem) timestamp; exit_p is reconstructed from
realized P&L over shares bought (avgPrice + realizedPnl/totalBought — exact
for a full single-price exit, share-weighted otherwise).
PAGING GOTCHA: /closed-positions serves at most 50 rows per page no
matter what `limit` says — step by the RETURNED page size, never by the
requested one (assuming limit-sized pages silently truncated every
wallet's exit history to its most recent 50 closes, which put a 16x
hold-to-res ceiling back into a scalper's stats). Stops at since_ts,
newest_bound (for incremental refresh: rows older than what's already
cached), max_rows, or an empty page. Prefer cache.closed_exits — the
incremental cached layer over this raw fetcher.
FULL HISTORY by default (since_ts=0): pages the wallet's ENTIRE closed
history — no window, no meaningful cap. The exit overlay covers every bet
the stats could ever touch; max_rows=200000 is only a runaway guard. The
cost is a one-time deep pull, amortized by the incremental exits cache
(cache.closed_exits) which then only pages new closes.
The real bound is `since_ts` (callers pass the window they score — 180d for
the sharps overlay, 30d for the backtest), so the pull covers exactly the
scored window and stops. max_rows=20000 is only a safety ceiling for a
pathological wallet; the date bound stops well before it for anyone real.
The original since_ts=0 (unbounded, all-history) pull is what stalled the
daily pipeline — bounding by date fixes completeness AND runtime."""
`reached_end` is True when the pull hit the TRUE start of the wallet's
history (an empty or short <50 page), False when it stopped early on a
bound (newest_bound for incremental refresh, or the max_rows guard). The
cache uses it to tell a COMPLETE backfill from an interrupted one, so a
killed deep pull re-completes instead of falsely reporting done.
PAGING GOTCHA: /closed-positions serves at most 50 rows per page no matter
what `limit` says — step by the RETURNED page size, never the requested
one (that silently truncated every wallet's history to its most recent 50
closes, reviving a 16x hold-to-res ceiling in scalper stats)."""
out = {}
off = 0
reached_end = False
while off < max_rows:
page = get_json("/closed-positions",
{"user": wallet, "limit": 500, "offset": off,
"sortBy": "TIMESTAMP", "sortDirection": "DESC"})
if not page:
reached_end = True # ran out of history — complete
break
for r in page:
ts = r.get("timestamp") or 0
@@ -148,11 +151,16 @@ def closed_exits(wallet, since_ts=0, max_rows=20000, newest_bound=0):
"ts": ts, "exit_p": exit_p, "p": max(0.001, min(0.999, avg)),
"iv": r.get("initialValue") or avg * tb, "cond": r.get("conditionId"),
"title": r.get("title") or "", "outcome": r.get("outcome") or ""})
oldest = page[-1].get("timestamp") or 0
if oldest < since_ts or oldest < newest_bound:
if len(page) < 50: # short page = start of history reached
reached_end = True
break
oldest = page[-1].get("timestamp") or 0
if since_ts and oldest < since_ts:
break # bounded stop — NOT the true end
if newest_bound and oldest < newest_bound:
break # incremental: reached already-cached rows
off += len(page) # actual page size — the server caps at 50
return out
return out, reached_end
WIN_WINDOW_DAYS = 90 # measure win rate over resolved bets in this window