Files
winning-wallet-finder_github/live/backtest_june.py
T
jaxperro 3d0bc7f001 Add live/ skilled-wallet scanner + cache; document clean OOS finding
live/: operationalizes the LBS/Yale "skilled ~3%" result against the live
data-api. Enumerate recent liquid markets -> top traders -> candidate pool;
cache every wallet's resolved bets once in DuckDB (~26k wallets / 12.5M bets,
keyed by per-bet resolution time so any cutoff re-scores in seconds); 5-gate
skill funnel (n>=15, z>0, BH-FDR, split-half OOS, MM/bot cap); dashboard +
daily refresh.

Key finding: copying the high-win-rate "favorite-rider" cohort looks +23.6%
in-sample but loses -7.4% once selected on pre-June-1 data only (99% -> 68%
win rate) — selection bias, reproducing the paper's "lucky winners revert"
result on live data. Win rate != edge, again.

wide/: bulk subgraph->DuckDB scanner (survivorship-bias-free over all wallets),
but the public subgraph is frozen at Jan 2026 -> historical tool only.

Large local data (*.duckdb, candidates.json, *_scored.json, history/) gitignored.
README + FINDINGS updated with the current logic and the clean result.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 11:16:20 -06:00

151 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""Forward copy-test: copy the FAVORITE-rider skilled wallets' new entries from
June 1 to now, $1000 bankroll, NO execution lag (we get their exact fill price).
Method: collect every BUY these wallets made on/after June 1 (data-api), take the
first entry per market (basket consensus, one position per market), deploy $1000
split equally across them, then settle each via the CLOB winner flag (resolved)
or mark to current price (still open). Reports realized + unrealized P&L.
python3 backtest_june.py # favorites, from 2026-06-01
python3 backtest_june.py value # test the value/longshot archetype instead
"""
import json
import os
import ssl
import sys
import time
import urllib.request
from concurrent.futures import ThreadPoolExecutor
HERE = os.path.dirname(__file__)
DATA = "https://data-api.polymarket.com"
CLOB = "https://clob.polymarket.com/markets"
CTX = ssl._create_unverified_context()
START = time.mktime(time.strptime("2026-06-01", "%Y-%m-%d"))
BANKROLL = 1000.0
ARCH = sys.argv[1] if len(sys.argv) > 1 else "favorite"
def get(url):
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
return json.loads(urllib.request.urlopen(req, timeout=30, context=CTX).read())
def trades_since(wallet):
"""All BUY trades on/after START for one wallet."""
out, off = [], 0
for _ in range(8):
try:
page = get(f"{DATA}/activity?user={wallet}&type=TRADE&limit=500&offset={off}")
except Exception:
break
if not page:
break
for t in page:
if (t.get("timestamp") or 0) < START:
return out
if t.get("side") == "BUY" and t.get("conditionId"):
out.append(t)
off += 500
if len(page) < 500:
break
return out
_mkt = {}
def market(cond):
if cond not in _mkt:
try:
_mkt[cond] = get(f"{CLOB}/{cond}")
except Exception:
_mkt[cond] = None
return _mkt[cond]
def settle(cond, outcome_idx, outcome_name):
"""-> (status, value_per_share). status in won/lost/open/unknown."""
m = market(cond)
if not m:
return "unknown", None
toks = m.get("tokens") or []
tok = None
if outcome_idx is not None and outcome_idx < len(toks):
tok = toks[outcome_idx]
if tok is None:
for t in toks:
if (t.get("outcome") or "").lower() == (outcome_name or "").lower():
tok = t; break
if tok is None:
return "unknown", None
if tok.get("winner") is True:
return "won", 1.0
if tok.get("winner") is False:
return "lost", 0.0
return "open", float(tok.get("price") or 0) # not resolved -> mark to price
def main():
wl = json.load(open(os.path.join(HERE, os.environ.get("BT_WATCH", "watch_skilled.json"))))
wallets = [w for w in wl if (w["avg_entry"] >= 0.85 if ARCH == "favorite"
else w["avg_entry"] < 0.5 if ARCH == "value"
else True)]
print(f"{ARCH}: {len(wallets)} wallets · copying BUYs from "
f"{time.strftime('%Y-%m-%d', time.localtime(START))} to now, ${BANKROLL:.0f}, no lag\n",
flush=True)
# gather every favorite's June+ buys, keep the FIRST entry per market
picks = {} # cond -> trade (earliest)
with ThreadPoolExecutor(max_workers=10) as ex:
for ts in ex.map(trades_since, [w["wallet"] for w in wallets]):
for t in ts:
c = t["conditionId"]
if c not in picks or t["timestamp"] < picks[c]["timestamp"]:
picks[c] = t
n = len(picks)
if not n:
print("no copied entries in the window."); return
stake = BANKROLL / n
print(f"{n} unique markets entered → ${stake:.2f} per position\n", flush=True)
won = lost = openc = unk = 0
realized = unreal_val = realized_cost = open_cost = 0.0
rows = []
with ThreadPoolExecutor(max_workers=10) as ex:
results = list(ex.map(
lambda kv: (kv[1], settle(kv[0], kv[1].get("outcomeIndex"), kv[1].get("outcome"))),
picks.items()))
for t, (status, vps) in results:
p = t.get("price") or 0.01
shares = stake / max(p, 0.001)
title = (t.get("title") or "")[:46]
if status == "won":
won += 1; realized += shares * 1.0; realized_cost += stake
rows.append((shares - stake, status, p, title))
elif status == "lost":
lost += 1; realized += 0.0; realized_cost += stake
rows.append((-stake, status, p, title))
elif status == "open":
openc += 1; unreal_val += shares * vps; open_cost += stake
rows.append((shares * vps - stake, status, p, title))
else:
unk += 1; unreal_val += stake; open_cost += stake # unknown -> hold at cost
realized_pl = realized - realized_cost
equity = realized + unreal_val + 0.0 # all $1000 deployed
total_pl = equity - BANKROLL
print(f"resolved: {won}W / {lost}L · still open: {openc} · unknown: {unk}")
print(f"REALIZED P&L: {realized_pl:+,.2f} (on ${realized_cost:,.0f} settled)")
print(f"open positions marked to market: ${unreal_val:,.2f} (cost ${open_cost:,.0f})")
print(f"\nFINAL EQUITY: ${equity:,.2f} TOTAL P&L: {total_pl:+,.2f} "
f"({100*total_pl/BANKROLL:+.1f}% on ${BANKROLL:.0f})\n")
rows.sort()
print("worst / best copied bets:")
for pl, st, p, title in rows[:4] + rows[-4:]:
print(f" {pl:+8.2f} {st:>5} @{p:.2f} {title}")
if __name__ == "__main__":
main()