Files
jaxperro 8958279ee8 cleanup: archive everything the live system no longer touches
- retired-infra/: Railway config, Mac launchd runner, GH-Actions cron
  (worker is Fly.io arn now; Actions cron last fired 2026-07-02)
- live-research/: the June selection experiments (strategy/followability/
  pnl_basket/pnl_focused/backtest_june/clean_test + outputs)
- us-venue/: the scrapped Polymarket-US listability probe (+ its
  env-gated ONLY_CONDS replay filter stays in portfolio.py — generally
  useful for subset replays)
- root sweeps hunt/huntwide/oos/copyback/watch.json -> archive/
- untracked logs/CSVs of dead experiments -> archive/local/ (gitignored)
- READMEs updated: Fly migration, geoblock gotcha, new file map

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 17:18:46 -04:00

163 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""Train/test wallet-selection study, entirely from the local cache.
TRAIN = bets resolved before --train-end (default 2026-05-30): pick wallets.
TEST = bets resolved on/after --test-start (default 2026-06-01), resolved only:
validate which picks were actually profitable to follow forward.
Lens: we have $1,000 and copy flat-size, so the key metric is COPY-ROI — the
mean per-bet return if we mirror each entry with the same stake:
win -> (1-p)/p per $1 (entry at price p pays out at 1)
loss -> -1
A wallet with positive copy-ROI would have made us money (before lag/fees).
We also compute z (beats entry prices) and the wallet's own $ ROI.
Selection gates (all on TRAIN):
* n >= MIN_N resolved bets
* z significant (Benjamini-Hochberg FDR)
* copy-ROI > 0 (copying them actually paid)
* consistent: positive copy-ROI in >= CONSISTENCY of monthly buckets
* copyable proxies: median bet size in [$5,$5000] (not dust, not whale),
not at the ~2000-bet HFT cap, not one-market-dependent
python3 strategy.py
"""
import math
import os
import sys
import time
from collections import defaultdict
import duckdb
HERE = os.path.dirname(__file__)
DB = os.path.join(HERE, "cache.duckdb")
TRAIN_END = time.mktime(time.strptime(os.environ.get("TRAIN_END", "2026-05-30"), "%Y-%m-%d"))
TEST_START = time.mktime(time.strptime(os.environ.get("TEST_START", "2026-06-01"), "%Y-%m-%d"))
MIN_N = 30 # train bets needed to judge a wallet
MIN_N_TEST = 5 # forward bets needed to report a forward number
CAP = 1990 # >= this looks API-capped (HFT/truncated) -> deprioritize
SIZE_LO, SIZE_HI = 5.0, 5000.0 # copyable median bet size ($)
CONSISTENCY = 0.6 # fraction of monthly buckets that must be profitable
FDR_Q = 0.05
def ret(p, won):
return (1 - p) / p if won else -1.0 # copy return per $1 staked
def metrics(bets):
n = len(bets)
wins = sum(1 for b in bets if b["won"])
exp = sum(b["p"] for b in bets)
var = sum(b["p"] * (1 - b["p"]) for b in bets) or 1e-9
z = (wins - exp) / math.sqrt(var)
copy_roi = sum(ret(b["p"], b["won"]) for b in bets) / n
staked = sum(b["size"] for b in bets) or 1e-9
dollars = sum((b["size"] * (1 - b["p"]) / b["p"] if b["won"] else -b["size"]) for b in bets)
return dict(n=n, wins=wins, win_rate=100 * wins / n, z=z, copy_roi=copy_roi,
own_roi=dollars / staked, exp=exp)
def consistency(bets):
"""fraction of calendar-month buckets (by res_t) with positive copy-ROI."""
buck = defaultdict(list)
for b in bets:
buck[time.strftime("%Y-%m", time.localtime(b["res_t"] or 0))].append(b)
months = [m for m in buck.values() if len(m) >= 5]
if not months:
return 0.0, 0
good = sum(1 for m in months if sum(ret(b["p"], b["won"]) for b in m) > 0)
return good / len(months), len(months)
def concentration(bets):
"""share of total copy-return coming from the single best market."""
by = defaultdict(float)
for b in bets:
by[b["cond"]] += ret(b["p"], b["won"])
tot = sum(v for v in by.values() if v > 0) or 1e-9
return max(by.values()) / tot if by else 1.0
def norm_sf(z):
return 0.5 * math.erfc(z / math.sqrt(2))
def main():
con = duckdb.connect(DB, read_only=True)
wallets = [r[0] for r in con.execute("SELECT DISTINCT wallet FROM bets").fetchall()]
print(f"loaded {len(wallets):,} wallets; train<{time.strftime('%F', time.localtime(TRAIN_END))}"
f" test>={time.strftime('%F', time.localtime(TEST_START))}\n", flush=True)
cand = []
for w in wallets:
rows = con.execute(
"SELECT won,p,res_t,size,cond FROM bets WHERE wallet=?", [w]).fetchall()
bets = [dict(won=x[0], p=max(0.001, min(0.999, x[1] or 0)),
res_t=x[2], size=x[3] or 0, cond=x[4]) for x in rows]
train = [b for b in bets if (b["res_t"] or 0) < TRAIN_END]
if len(train) < MIN_N:
continue
m = metrics(train)
med = sorted(b["size"] for b in train)[len(train) // 2]
cons, nmonths = consistency(train)
conc = concentration(train)
test = [b for b in bets if (b["res_t"] or 0) >= TEST_START]
tm = metrics(test) if len(test) >= MIN_N_TEST else None
cand.append(dict(w=w, m=m, med=med, cons=cons, nmonths=nmonths, conc=conc,
capped=len(train) >= CAP, tm=tm, ntest=len(test)))
# FDR over the candidate edge p-values
pvals = sorted(norm_sf(c["m"]["z"]) for c in cand)
k = 0
for i, p in enumerate(pvals, 1):
if p <= FDR_Q * i / len(pvals):
k = i
thr = pvals[k - 1] if k else 0.0
# selection gates (all on TRAIN)
sel = [c for c in cand if
norm_sf(c["m"]["z"]) <= thr and thr > 0 and
c["m"]["copy_roi"] > 0 and
c["cons"] >= CONSISTENCY and
SIZE_LO <= c["med"] <= SIZE_HI and
not c["capped"] and
c["conc"] < 0.5]
sel.sort(key=lambda c: c["m"]["copy_roi"], reverse=True)
print(f"{len(cand):,} wallets with >= {MIN_N} train bets · BH thr p<= {thr:.1e}")
print(f"SELECTED (skilled + consistent + copyable on TRAIN): {len(sel)}\n")
# how did the SELECTED set do FORWARD?
fwd = [c for c in sel if c["tm"]]
if fwd:
avg_fwd = sum(c["tm"]["copy_roi"] for c in fwd) / len(fwd)
win_fwd = sum(1 for c in fwd if c["tm"]["copy_roi"] > 0)
print(f"FORWARD (June1+, resolved): {len(fwd)} selected wallets had test bets · "
f"{win_fwd}/{len(fwd)} stayed profitable · mean fwd copy-ROI {avg_fwd:+.1%}\n")
h = (f"{'train_roi':>10}{'fwd_roi':>9}{'tr_z':>6}{'tr_wr':>6}{'cons':>6}"
f"{'medSz':>7}{'tr_n':>6}{'fwd_n':>6} wallet")
print(h); print("-" * len(h))
for c in sel[:40]:
t = c["tm"]
fr = f"{t['copy_roi']:+.0%}" if t else " —"
fn = c["ntest"] if t else 0
print(f"{c['m']['copy_roi']:>+9.0%}{fr:>9}{c['m']['z']:>6.1f}{c['m']['win_rate']:>5.0f}%"
f"{c['cons']:>6.0%}{c['med']:>7.0f}{c['m']['n']:>6}{fn:>6} {c['w']}")
# persist the selection for the followability pull + watchlist
import json
json.dump([{"wallet": c["w"], "train_copy_roi": round(c["m"]["copy_roi"], 4),
"train_z": round(c["m"]["z"], 2), "train_n": c["m"]["n"],
"fwd_copy_roi": round(c["tm"]["copy_roi"], 4) if c["tm"] else None,
"fwd_n": c["ntest"], "med_size": round(c["med"], 1),
"consistency": round(c["cons"], 2)} for c in sel],
open(os.path.join(HERE, "selection.json"), "w"), indent=2)
print(f"\n-> selection.json ({len(sel)} wallets)")
if __name__ == "__main__":
main()