Files
winning-wallet-finder_github/oos.py
T
jaxperro f176ba134a Rebrand to Winning Wallet Finder; commit validation scripts; dev-friendly docs
- Add copyback.py (in-sample copy backtest), oos.py (out-of-sample test),
  huntwide.py (wide insider sweep) — completes the detect→hunt→validate→watch
  pipeline.
- Rewrite README around Winning Wallet Finder: the z-score idea explained, how
  the pieces fit, quickstart, data sources, live-watcher setup, honest verdict.
- Extend FINDINGS with the insider-detection results and the in-sample vs
  out-of-sample copy verdict (+545% in-sample collapsed to one-wallet variance
  out-of-sample).
- Refresh config.example.json to the current schema (discord/alchemy/watch).

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

154 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Out-of-sample test of the copy-the-edge-wallets strategy.
SELECT wallets using only resolved bets in [Feb 1 - Apr 30] (z-score), with NO
knowledge of May-June. Then COPY those wallets' entries forward from May 30,
compounding. If the forward return is strong, the edge is real; if it collapses,
the in-sample +545% was selection theater.
"""
import math
import time
from collections import defaultdict
import smart_money as sm
from insider import resolved_bets, norm_sf
from copyback import entries_after, outcomes, _parse, BET_K, START_BANKROLL
SEL_T0 = _parse("2026-02-01") # selection window start (resolution time)
SEL_T1 = _parse("2026-04-30") # selection window end — nothing after this is seen
TEST_START = _parse("2026-05-30") # copy entries on/after this
Z_PICK = 4.0 # insider-grade cut, judged AS OF Apr 30
MIN_BETS = 15
def score_pre(wallet):
"""z-score over resolved bets in the selection window only."""
bets = [b for b in resolved_bets(wallet, SEL_T0 - 10 * 86400)
if SEL_T0 <= b["res_t"] <= SEL_T1]
if len(bets) < MIN_BETS:
return None
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)
return {"wallet": wallet, "n": len(bets), "wins": wins, "z": z, "pval": norm_sf(z)}
def candidate_pool():
import csv
seen = {}
try:
for r in csv.DictReader(open("huntwide.csv")):
seen[r["wallet"]] = r["username"]
except FileNotFoundError:
pass
return seen
def main():
from concurrent.futures import ThreadPoolExecutor, as_completed
pool = candidate_pool()
print(f"candidate pool: {len(pool)} wallets · scoring on [Feb1Apr30] only...", flush=True)
selected = []
with ThreadPoolExecutor(max_workers=12) as ex:
futs = {ex.submit(score_pre, w): w for w in pool}
done = 0
for f in as_completed(futs):
done += 1
try:
r = f.result()
except Exception:
r = None
if r and r["z"] >= Z_PICK:
r["name"] = pool[r["wallet"]]
selected.append(r)
if done % 50 == 0:
print(f" {done}/{len(pool)}", flush=True)
selected.sort(key=lambda r: r["z"], reverse=True)
print(f"\nINSIDER-GRADE AS OF APR 30 (z>={Z_PICK}): {len(selected)} wallets")
for r in selected:
print(f" {r['name'][:18]:18} z={r['z']:>4.1f} p={r['pval']:.1e} "
f"{r['wins']}/{r['n']} (pre-period)")
if not selected:
print("\nNo wallets were insider-grade as of Apr 30 — the edge wallets are "
"too new to have a pre-period track record. That itself is the answer.")
return
# forward copy from May 30, z(pre)-weighted, compounding
tot_z = sum(r["z"] for r in selected)
weights = {r["wallet"]: r["z"] / tot_z for r in selected}
names = {r["wallet"]: r["name"] for r in selected}
print(f"\ncopying {len(selected)} wallets forward from 2026-05-30 "
f"(z-pre weighted, compounding)...", flush=True)
bets = []
now = time.time()
for r in selected:
w = r["wallet"]
ent = entries_after(w, TEST_START)
outc = outcomes(w)
for a, (price, ts, title) in ent.items():
if a not in outc or not (0 < price < 1):
continue
cur, end = outc[a]
bets.append({"w": w, "ts": ts, "price": price, "mark": cur,
"res_ts": end or now, "resolved": bool(end and end < now)})
print(f"forward copied bets: {len(bets)}", flush=True)
events = []
for i, b in enumerate(bets):
events.append((b["ts"], 0, i))
if b["resolved"]:
events.append((b["res_ts"], 1, i))
events.sort()
cash = START_BANKROLL
open_cost = 0.0
posn = {}
wins = losses = 0
realized = 0.0
bw = defaultdict(float)
for ts, kind, i in events:
b = bets[i]
if kind == 0:
bankroll = cash + open_cost
stake = min(weights[b["w"]] * BET_K * bankroll, cash)
if stake < 1:
continue
posn[i] = (stake / b["price"], stake)
cash -= stake
open_cost += stake
else:
if i not in posn:
continue
shares, stake = posn.pop(i)
payout = shares * (1 if b["mark"] >= 0.5 else 0)
cash += payout
open_cost -= stake
realized += payout - stake
bw[names[b["w"]]] += payout - stake
wins += b["mark"] >= 0.5
losses += b["mark"] < 0.5
open_val = sum(sh * bets[i]["mark"] for i, (sh, st) in posn.items())
equity = cash + open_val
print(f"\n{'='*64}")
print(f" OUT-OF-SAMPLE forward result (selection knew nothing past Apr 30)")
print(f" resolved: {wins+losses} ({wins}W/{losses}L"
f"{f' · {wins/(wins+losses)*100:.0f}%' if wins+losses else ''}) "
f"· open: {len(posn)}")
print(f" realized P&L: ${realized:+,.2f} ({realized/START_BANKROLL*100:+.1f}%)")
print(f" unrealized: ${open_val-open_cost:+,.2f}")
print(f" ── ending equity ${equity:,.2f} -> {(equity/START_BANKROLL-1)*100:+.1f}% "
f"on $1,000 over {(now-TEST_START)/86400:.0f}d")
print(f"{'='*64}")
for n in sorted(bw, key=lambda k: bw[k], reverse=True):
print(f" {n[:18]:18} {bw[n]:+,.2f}")
if __name__ == "__main__":
main()