mirror of
https://github.com/jaxperro/winning-wallet-finder.git
synced 2026-07-28 00:07:47 +00:00
Add edge-research tooling and document the full research log
Adds edge_research.py (scan ~2000 wallets for reliable/copyable weekly edge), lookback.py (long-window half-split out-of-sample read), and table_77.py (aggregate a wallet set to CSV). README now leads with a research log capturing the key findings: win-rate survivorship bias, win-rate != EV, flat-size copying is -EV, the reliable edge is rare and skews to young accounts, and ROI is inversely related to bet size. Generated data files are gitignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,3 +7,8 @@ config.json
|
||||
copytrade_state.json
|
||||
*.log
|
||||
*.tmp
|
||||
|
||||
# generated research data (regenerable via edge_research.py / table_77.py)
|
||||
edge_metrics.jsonl
|
||||
edge_profitable.json
|
||||
copyable_77.csv
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
# Polymarket Smart Money
|
||||
|
||||
Finds Polymarket wallets that **win more than 75% of their resolved bets** and
|
||||
**bet multiple times per week** — the "smart money" worth watching.
|
||||
Tools to find Polymarket wallets worth following, copy-trade them (paper or
|
||||
live), and backtest the strategy. Zero dependencies — Python 3 stdlib only
|
||||
(except live trading, which needs `py-clob-client`).
|
||||
|
||||
Zero dependencies. One file, Python 3 stdlib only.
|
||||
> **Start here — read [What we learned](#what-we-learned-research-log).** The
|
||||
> project began as "find wallets winning >75% of their bets." That metric turned
|
||||
> out to be an artifact, and the research below changed what we actually measure.
|
||||
> Don't fund anything before reading it.
|
||||
|
||||
## Tools
|
||||
|
||||
| File | What it does |
|
||||
|------|--------------|
|
||||
| `smart_money.py` | Dashboard + scanner. Ranks leaderboard wallets by **true** win rate. |
|
||||
| `edge_research.py` | Scans up to ~2000 wallets for a reliable, copyable weekly edge (consistency metrics + copyability). |
|
||||
| `lookback.py` | Deep-dive a short list over a long window, split into halves for out-of-sample reads. |
|
||||
| `table_77.py` | Aggregate a filtered wallet set into one CSV (ROI, total staked, consistency). |
|
||||
| `copytrade.py` | Copy-trade engine — mirror a watchlist (paper by default, live gated). |
|
||||
| `backtest.py` | Replay a watchlist over a recent window and mark outcomes. |
|
||||
|
||||
## Run the dashboard
|
||||
|
||||
@@ -126,3 +141,62 @@ and runtime state never get committed.
|
||||
- Very high-volume / market-maker wallets (thousands of fills) can't be cleanly
|
||||
backtested via the public API — too many fills, no historical position
|
||||
snapshot.
|
||||
|
||||
## What we learned (research log)
|
||||
|
||||
The honest story of what the data showed, in order. Each finding killed an
|
||||
assumption the previous step relied on.
|
||||
|
||||
**1. Win rate was an illusion (survivorship bias).**
|
||||
Polymarket only redeems *winning* shares; losing shares are worth $0 and sit
|
||||
unredeemed in `/positions` at `curPrice 0` forever, never entering
|
||||
`/closed-positions`. Measuring win rate over `/closed-positions` alone counts
|
||||
almost only winners. Examples: NiNo999 read 90.6%, true rate **48.3%**; Boggs
|
||||
read 73.4%, true **50.3%**. Fixed by unioning both endpoints over a window.
|
||||
**Takeaway: a high reported win rate is a red flag for a bias bug, not a sharp.**
|
||||
|
||||
**2. With the honest metric, nobody wins 75%.**
|
||||
Across 25 top wallets, true win rates clustered at a **median 49%** (coin flip),
|
||||
max ~60%. Zero passed a 75% bar. Win rate is the wrong thing to rank on.
|
||||
|
||||
**3. Win rate ≠ profit; raw PnL ≠ reliability.**
|
||||
`surfandturf` won 54.8% and made millions; `Latina` (leaderboard #1 all-time)
|
||||
won 43% and was **−$3.8M over 90 days**. And wallets with big total PnL often
|
||||
got there on one or two outlier weeks (38% green weeks) — a lottery, not an
|
||||
edge. The signal that finds reliable money is **weekly consistency**: % of weeks
|
||||
green, profit factor, weekly Sharpe — measured per week, with enough weeks.
|
||||
|
||||
**4. Flat-size copy-trading is −EV.**
|
||||
A 7-day backtest of four "top" wallets returned **−48%**. At ~50% entry hit
|
||||
rates, mirroring entries at flat size just pays the spread on coin flips. A
|
||||
profitable wallet's edge lives in *sizing and entry prices*, which copying
|
||||
entries does not reproduce. (The backtest also exposed a missing per-position
|
||||
cap — proportional adds could balloon one market to the whole exposure limit.)
|
||||
|
||||
**5. A reliable edge looks real but rare — and skews young.**
|
||||
Scanning 1,500 wallets over 120 days: 1,017 had history, 199 passed a
|
||||
consistency screen, **77 were copyable** (hold-to-resolution ≥70%). But ~7.5%
|
||||
of wallets passing by chance is exactly what randomness produces over 1,017
|
||||
coin-flippers — so some of the 77 are luck. Worse, a 240-day lookback showed
|
||||
the "best" wallets are **young accounts** (surfandturf's oldest bet: 72 days;
|
||||
joblessfinalboss: 79). New accounts that get hot rise to the leaderboard and
|
||||
pass the screen; the ones that flamed out are delisted. **The most impressive
|
||||
short-term performers are the least trustworthy.**
|
||||
|
||||
**6. ROI and size are inversely related.**
|
||||
Among the 77 copyable wallets, the highest-ROI ones bet small (dnte: 57% ROI on
|
||||
$109K), while the biggest bettors scalp thin edges (elmcap2: $114M staked,
|
||||
**0.4%** ROI). `surfandturf` was the lone anomaly — big *and* high-ROI ($27.9M
|
||||
staked at 16%) — which makes it either the best find or the biggest variance
|
||||
story. At 72 days old, we can't yet tell.
|
||||
|
||||
### Where this leaves the strategy
|
||||
|
||||
- **Rank on risk-adjusted consistency** (% green weeks × profit factor ×
|
||||
Sharpe), never win rate or raw PnL.
|
||||
- **Require account longevity** — distrust anything under ~4–6 months.
|
||||
- **Validate out-of-sample** (walk-forward: select on an early window, measure a
|
||||
later one) before trusting any wallet list. This is the decisive open step.
|
||||
- **Copying entries ≠ copying edge.** A working strategy likely needs to model
|
||||
sizing/pricing, or pivot to a consensus signal (bet where many vetted wallets
|
||||
agree) rather than blind mirroring.
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Scan many wallets for a RELIABLE, COPYABLE weekly edge.
|
||||
|
||||
Two passes:
|
||||
1. metrics — for every candidate, bucket resolved-bet PnL by week over the
|
||||
window and compute consistency (% green weeks, profit factor, Sharpe, ROI).
|
||||
Results stream to a JSONL file so a long run is crash-safe.
|
||||
2. copyability — for the wallets that look profitable, pull /activity and
|
||||
measure how much they hold to resolution (mirrorable) vs trade around
|
||||
(not mirrorable by copying entries).
|
||||
|
||||
python3 edge_research.py --pool 1500 --days 120
|
||||
|
||||
Outputs: edge_metrics.jsonl (raw, all wallets)
|
||||
edge_profitable.json (filtered + copyability, ranked)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import statistics
|
||||
import sys
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
import smart_money as sm
|
||||
|
||||
WEEK = 7 * 86400
|
||||
MAX_PAGES = 40 # per endpoint, bounds runtime on hyperactive wallets
|
||||
|
||||
|
||||
def _parse_end(end):
|
||||
if not end:
|
||||
return 0
|
||||
end = end.replace("Z", "")
|
||||
for fmt in ("%Y-%m-%dT%H:%M:%S", "%Y-%m-%d"):
|
||||
try:
|
||||
return time.mktime(time.strptime(end, fmt))
|
||||
except ValueError:
|
||||
continue
|
||||
return 0
|
||||
|
||||
|
||||
def candidates(pool):
|
||||
seen = {}
|
||||
for window in ("7d", "30d", "all"):
|
||||
offset = 0
|
||||
while offset < pool and offset < 2000:
|
||||
page = sm.get_json("/v1/leaderboard",
|
||||
{"window": window, "limit": 50, "offset": offset})
|
||||
if not page:
|
||||
break
|
||||
for u in page:
|
||||
w = u.get("proxyWallet")
|
||||
if w and w not in seen:
|
||||
seen[w] = {"wallet": w,
|
||||
"username": u.get("userName") or w[:10] + "...",
|
||||
"lb_pnl": u.get("pnl", 0)}
|
||||
offset += 50
|
||||
if len(page) < 50:
|
||||
break
|
||||
if len(seen) >= pool:
|
||||
break
|
||||
return list(seen.values())[:pool]
|
||||
|
||||
|
||||
def resolved_with_stake(wallet, cutoff):
|
||||
now = time.time()
|
||||
out = []
|
||||
off = 0
|
||||
while off < MAX_PAGES * 50:
|
||||
page = sm.get_json("/closed-positions",
|
||||
{"user": wallet, "limit": 50, "offset": off,
|
||||
"sortBy": "TIMESTAMP", "sortDirection": "DESC"})
|
||||
if not page:
|
||||
break
|
||||
for p in page:
|
||||
if p.get("timestamp", 0) >= cutoff:
|
||||
out.append({"ts": p["timestamp"], "pnl": p.get("realizedPnl", 0),
|
||||
"stake": p.get("avgPrice", 0) * p.get("totalBought", 0)})
|
||||
off += 50
|
||||
if len(page) < 50 or page[-1].get("timestamp", 0) < cutoff:
|
||||
break
|
||||
off = 0
|
||||
while off < MAX_PAGES * 50:
|
||||
page = sm.get_json("/positions",
|
||||
{"user": wallet, "limit": 50, "offset": off,
|
||||
"sizeThreshold": 0.0})
|
||||
if not page:
|
||||
break
|
||||
for p in page:
|
||||
end = _parse_end(p.get("endDate"))
|
||||
if cutoff <= end < now:
|
||||
out.append({"ts": end, "pnl": p.get("cashPnl", 0),
|
||||
"stake": p.get("initialValue", 0)})
|
||||
off += 50
|
||||
if len(page) < 50:
|
||||
break
|
||||
return out
|
||||
|
||||
|
||||
def metrics(cand, cutoff):
|
||||
bets = resolved_with_stake(cand["wallet"], cutoff)
|
||||
if len(bets) < 20:
|
||||
return None
|
||||
by_week = defaultdict(lambda: [0.0, 0.0])
|
||||
for b in bets:
|
||||
wk = int(b["ts"] // WEEK)
|
||||
by_week[wk][0] += b["pnl"]
|
||||
by_week[wk][1] += b["stake"]
|
||||
weeks = sorted(by_week)
|
||||
wpnl = [by_week[w][0] for w in weeks]
|
||||
wroi = [by_week[w][0] / by_week[w][1] if by_week[w][1] else 0 for w in weeks]
|
||||
total_pnl = sum(wpnl)
|
||||
total_stake = sum(by_week[w][1] for w in weeks)
|
||||
gw = sum(p for p in wpnl if p > 0)
|
||||
gl = abs(sum(p for p in wpnl if p < 0))
|
||||
mean_roi = statistics.mean(wroi)
|
||||
std_roi = statistics.pstdev(wroi) if len(wroi) > 1 else 0
|
||||
return {
|
||||
"wallet": cand["wallet"], "username": cand["username"],
|
||||
"lb_pnl": round(cand["lb_pnl"]),
|
||||
"n_weeks": len(weeks), "n_bets": len(bets),
|
||||
"pct_weeks_pos": round(sum(1 for p in wpnl if p > 0) / len(weeks) * 100),
|
||||
"mean_weekly_roi": round(mean_roi * 100, 1),
|
||||
"weekly_sharpe": round(mean_roi / std_roi, 2) if std_roi else 0,
|
||||
"profit_factor": round(gw / gl, 2) if gl else 999,
|
||||
"total_pnl": round(total_pnl),
|
||||
"total_roi": round(total_pnl / total_stake * 100, 1) if total_stake else 0,
|
||||
}
|
||||
|
||||
|
||||
def copyability(wallet):
|
||||
trades, off = [], 0
|
||||
while off < 2000: # cap fills for speed
|
||||
p = sm.get_json("/activity",
|
||||
{"user": wallet, "type": "TRADE", "limit": 500, "offset": off})
|
||||
if not p:
|
||||
break
|
||||
trades += p
|
||||
off += 500
|
||||
if len(p) < 500:
|
||||
break
|
||||
by_mkt = defaultdict(lambda: {"buy_usd": 0.0, "sell_usd": 0.0, "sold": False})
|
||||
for t in trades:
|
||||
m = by_mkt[t.get("conditionId")]
|
||||
if t.get("side") == "BUY":
|
||||
m["buy_usd"] += t.get("usdcSize", 0)
|
||||
else:
|
||||
m["sell_usd"] += t.get("usdcSize", 0)
|
||||
m["sold"] = True
|
||||
n = len(by_mkt) or 1
|
||||
hold = sum(1 for m in by_mkt.values() if not m["sold"])
|
||||
return {"markets": len(by_mkt), "hold_pct": round(hold / n * 100),
|
||||
"fills": len(trades)}
|
||||
|
||||
|
||||
def run(pool, days, workers):
|
||||
cutoff = time.time() - days * 86400
|
||||
out_path, prof_path = "edge_metrics.jsonl", "edge_profitable.json"
|
||||
print(f"[{time.strftime('%H:%M:%S')}] pulling up to {pool} candidates...", flush=True)
|
||||
cands = candidates(pool)
|
||||
print(f"[{time.strftime('%H:%M:%S')}] {len(cands)} candidates · "
|
||||
f"window {days}d · analyzing (workers={workers})", flush=True)
|
||||
|
||||
done = kept = 0
|
||||
with open(out_path, "w") as fout, ThreadPoolExecutor(max_workers=workers) as ex:
|
||||
futs = {ex.submit(metrics, c, cutoff): c for c in cands}
|
||||
for f in as_completed(futs):
|
||||
done += 1
|
||||
try:
|
||||
r = f.result()
|
||||
except Exception:
|
||||
r = None
|
||||
if r:
|
||||
kept += 1
|
||||
fout.write(json.dumps(r) + "\n")
|
||||
fout.flush()
|
||||
if done % 50 == 0 or done == len(cands):
|
||||
print(f"[{time.strftime('%H:%M:%S')}] {done}/{len(cands)} analyzed "
|
||||
f"· {kept} with enough history", flush=True)
|
||||
|
||||
rows = [json.loads(l) for l in open(out_path)]
|
||||
# "looks profitable" screen
|
||||
prof = [r for r in rows if r["n_weeks"] >= max(4, days // 7 * 0.4)
|
||||
and r["n_bets"] >= 30 and r["total_pnl"] > 0 and r["total_roi"] > 0
|
||||
and r["pct_weeks_pos"] >= 60 and r["profit_factor"] >= 1.3]
|
||||
print(f"\n[{time.strftime('%H:%M:%S')}] {len(prof)} wallets pass the profitable "
|
||||
f"screen · checking copyability...", flush=True)
|
||||
|
||||
with ThreadPoolExecutor(max_workers=workers) as ex:
|
||||
futs = {ex.submit(copyability, r["wallet"]): r for r in prof}
|
||||
for f in as_completed(futs):
|
||||
r = futs[f]
|
||||
try:
|
||||
r["copy"] = f.result()
|
||||
except Exception:
|
||||
r["copy"] = {"markets": 0, "hold_pct": 0, "fills": 0}
|
||||
|
||||
for r in prof:
|
||||
r["copyable"] = r["copy"]["hold_pct"] >= 70
|
||||
# composite: reward consistency, profit factor, and ROI
|
||||
r["score"] = round(r["pct_weeks_pos"] / 100 * r["profit_factor"]
|
||||
* (1 + r["total_roi"] / 100), 2)
|
||||
prof.sort(key=lambda r: (r["copyable"], r["score"]), reverse=True)
|
||||
json.dump(prof, open(prof_path, "w"), indent=2)
|
||||
|
||||
print(f"\n{'='*94}")
|
||||
print(f" PROFITABLE & COPYABLE wallets (window {days}d, pool {len(cands)})")
|
||||
print(f"{'='*94}")
|
||||
h = (f"{'Trader':<20}{'wks':>4}{'bets':>6}{'%wk+':>6}{'PF':>6}"
|
||||
f"{'Sharpe':>7}{'totROI':>8}{'hold%':>7}{'copy':>6}{'90d PnL':>13}")
|
||||
print(h)
|
||||
print("-" * len(h))
|
||||
for r in prof:
|
||||
print(f"{r['username'][:20]:<20}{r['n_weeks']:>4}{r['n_bets']:>6}"
|
||||
f"{r['pct_weeks_pos']:>5}%{r['profit_factor']:>6.2f}"
|
||||
f"{r['weekly_sharpe']:>7.2f}{r['total_roi']:>7}%"
|
||||
f"{r['copy']['hold_pct']:>6}%{'yes' if r['copyable'] else 'no':>6}"
|
||||
f"{'$'+format(r['total_pnl'], ','):>13}")
|
||||
print("-" * len(h))
|
||||
cop = sum(1 for r in prof if r["copyable"])
|
||||
print(f"{len(prof)} profitable · {cop} of them copyable (hold-to-resolution ≥70%)")
|
||||
print(f"Full detail: {prof_path}\n")
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument("--pool", type=int, default=1500)
|
||||
ap.add_argument("--days", type=int, default=120)
|
||||
ap.add_argument("--workers", type=int, default=12)
|
||||
args = ap.parse_args()
|
||||
run(args.pool, args.days, args.workers)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""240-day lookback on a short list of wallets, split into halves.
|
||||
|
||||
We selected these wallets on their last 120 days. The *older* half (240->120
|
||||
days ago) is data that played no part in selection — so consistency there is
|
||||
backward out-of-sample evidence the edge is real, not a lucky recent stretch.
|
||||
"""
|
||||
|
||||
import statistics
|
||||
import sys
|
||||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
import smart_money as sm
|
||||
|
||||
WEEK = 7 * 86400
|
||||
PAGES = 160 # generous for this focused 5-wallet run
|
||||
|
||||
|
||||
def parse_end(end):
|
||||
if not end:
|
||||
return 0
|
||||
end = end.replace("Z", "")
|
||||
for fmt in ("%Y-%m-%dT%H:%M:%S", "%Y-%m-%d"):
|
||||
try:
|
||||
return time.mktime(time.strptime(end, fmt))
|
||||
except ValueError:
|
||||
continue
|
||||
return 0
|
||||
|
||||
|
||||
def resolved(wallet, cutoff):
|
||||
now = time.time()
|
||||
out = []
|
||||
off = 0
|
||||
while off < PAGES * 50:
|
||||
page = sm.get_json("/closed-positions",
|
||||
{"user": wallet, "limit": 50, "offset": off,
|
||||
"sortBy": "TIMESTAMP", "sortDirection": "DESC"})
|
||||
if not page:
|
||||
break
|
||||
for p in page:
|
||||
if p.get("timestamp", 0) >= cutoff:
|
||||
out.append({"ts": p["timestamp"], "pnl": p.get("realizedPnl", 0),
|
||||
"stake": p.get("avgPrice", 0) * p.get("totalBought", 0)})
|
||||
off += 50
|
||||
if len(page) < 50 or page[-1].get("timestamp", 0) < cutoff:
|
||||
break
|
||||
off = 0
|
||||
while off < PAGES * 50:
|
||||
page = sm.get_json("/positions",
|
||||
{"user": wallet, "limit": 50, "offset": off,
|
||||
"sizeThreshold": 0.0})
|
||||
if not page:
|
||||
break
|
||||
for p in page:
|
||||
end = parse_end(p.get("endDate"))
|
||||
if cutoff <= end < now:
|
||||
out.append({"ts": end, "pnl": p.get("cashPnl", 0),
|
||||
"stake": p.get("initialValue", 0)})
|
||||
off += 50
|
||||
if len(page) < 50:
|
||||
break
|
||||
return out
|
||||
|
||||
|
||||
def stats(bets):
|
||||
if not bets:
|
||||
return None
|
||||
by_week = defaultdict(lambda: [0.0, 0.0])
|
||||
for b in bets:
|
||||
wk = int(b["ts"] // WEEK)
|
||||
by_week[wk][0] += b["pnl"]
|
||||
by_week[wk][1] += b["stake"]
|
||||
weeks = sorted(by_week)
|
||||
wpnl = [by_week[w][0] for w in weeks]
|
||||
wroi = [by_week[w][0] / by_week[w][1] if by_week[w][1] else 0 for w in weeks]
|
||||
tot_pnl = sum(wpnl)
|
||||
tot_stake = sum(by_week[w][1] for w in weeks)
|
||||
gw = sum(p for p in wpnl if p > 0)
|
||||
gl = abs(sum(p for p in wpnl if p < 0))
|
||||
mean = statistics.mean(wroi)
|
||||
std = statistics.pstdev(wroi) if len(wroi) > 1 else 0
|
||||
return {
|
||||
"weeks": len(weeks), "bets": len(bets),
|
||||
"green": round(sum(1 for p in wpnl if p > 0) / len(weeks) * 100),
|
||||
"pf": round(gw / gl, 2) if gl else 999,
|
||||
"sharpe": round(mean / std, 2) if std else 0,
|
||||
"roi": round(tot_pnl / tot_stake * 100, 1) if tot_stake else 0,
|
||||
"pnl": round(tot_pnl),
|
||||
}
|
||||
|
||||
|
||||
def line(label, s):
|
||||
if not s:
|
||||
print(f" {label:<8} (no resolved bets in this period)")
|
||||
return
|
||||
print(f" {label:<8} {s['weeks']:>2}wk {s['bets']:>5}bets "
|
||||
f"{s['green']:>3}%grn PF {s['pf']:>6} Sharpe {s['sharpe']:>5} "
|
||||
f"ROI {s['roi']:>6}% ${s['pnl']:>12,}")
|
||||
|
||||
|
||||
def main(wallets):
|
||||
now = time.time()
|
||||
mid = now - 120 * 86400
|
||||
for name, w in wallets:
|
||||
bets = resolved(w, now - 240 * 86400)
|
||||
older = [b for b in bets if b["ts"] < mid] # 240->120d (not used to select)
|
||||
recent = [b for b in bets if b["ts"] >= mid] # 120->0d (selection window)
|
||||
print(f"\n{name} ({w[:16]}…)")
|
||||
line("240d all", stats(bets))
|
||||
line("older½", stats(older)) # out-of-sample
|
||||
line("recent½", stats(recent)) # in-sample
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# name, wallet — passed as alternating argv or hardcoded by caller
|
||||
pairs = [(sys.argv[i], sys.argv[i + 1]) for i in range(1, len(sys.argv), 2)]
|
||||
main(pairs)
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Aggregate the 77 copyable wallets into one table: total staked, PnL, ROI,
|
||||
consistency. Prints sorted by ROI and writes copyable_77.csv."""
|
||||
|
||||
import csv
|
||||
import json
|
||||
import statistics
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
import smart_money as sm
|
||||
from lookback import resolved # reuse the 120d+ resolved-bet puller
|
||||
|
||||
WEEK = 7 * 86400
|
||||
|
||||
|
||||
def compute(r):
|
||||
cutoff = time.time() - 120 * 86400
|
||||
bets = resolved(r["wallet"], cutoff)
|
||||
if not bets:
|
||||
return None
|
||||
by_week = defaultdict(lambda: [0.0, 0.0])
|
||||
for b in bets:
|
||||
wk = int(b["ts"] // WEEK)
|
||||
by_week[wk][0] += b["pnl"]
|
||||
by_week[wk][1] += b["stake"]
|
||||
weeks = sorted(by_week)
|
||||
wpnl = [by_week[w][0] for w in weeks]
|
||||
wroi = [by_week[w][0] / by_week[w][1] if by_week[w][1] else 0 for w in weeks]
|
||||
total_bet = sum(b["stake"] for b in bets)
|
||||
total_pnl = sum(b["pnl"] for b in bets)
|
||||
gw = sum(p for p in wpnl if p > 0)
|
||||
gl = abs(sum(p for p in wpnl if p < 0))
|
||||
mean = statistics.mean(wroi)
|
||||
std = statistics.pstdev(wroi) if len(wroi) > 1 else 0
|
||||
oldest_days = round((time.time() - min(b["ts"] for b in bets)) / 86400)
|
||||
return {
|
||||
"username": r["username"], "wallet": r["wallet"],
|
||||
"weeks": len(weeks), "bets": len(bets),
|
||||
"total_bet": round(total_bet), "total_pnl": round(total_pnl),
|
||||
"roi_pct": round(total_pnl / total_bet * 100, 1) if total_bet else 0,
|
||||
"pct_weeks_green": round(sum(1 for p in wpnl if p > 0) / len(weeks) * 100),
|
||||
"profit_factor": round(gw / gl, 2) if gl else 999,
|
||||
"weekly_sharpe": round(mean / std, 2) if std else 0,
|
||||
"hold_pct": r["copy"]["hold_pct"],
|
||||
"history_days": oldest_days,
|
||||
"avg_bet": round(total_bet / len(bets)) if bets else 0,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
cop = [r for r in json.load(open("edge_profitable.json")) if r.get("copyable")]
|
||||
out = []
|
||||
with ThreadPoolExecutor(max_workers=12) as ex:
|
||||
futs = {ex.submit(compute, r): r for r in cop}
|
||||
for f in as_completed(futs):
|
||||
r = f.result()
|
||||
if r:
|
||||
out.append(r)
|
||||
out.sort(key=lambda r: r["roi_pct"], reverse=True)
|
||||
|
||||
cols = ["username", "roi_pct", "total_bet", "total_pnl", "avg_bet",
|
||||
"pct_weeks_green", "profit_factor", "weekly_sharpe", "weeks",
|
||||
"bets", "hold_pct", "history_days", "wallet"]
|
||||
with open("copyable_77.csv", "w", newline="") as f:
|
||||
w = csv.DictWriter(f, fieldnames=cols)
|
||||
w.writeheader()
|
||||
w.writerows(out)
|
||||
|
||||
print(f"{'#':>3} {'Trader':<20}{'ROI%':>7}{'TotalBet':>13}{'TotalPnL':>13}"
|
||||
f"{'AvgBet':>9}{'%grn':>6}{'PF':>6}{'Shrp':>6}{'wks':>4}{'hist_d':>7}")
|
||||
print("-" * 100)
|
||||
for i, r in enumerate(out, 1):
|
||||
print(f"{i:>3} {r['username'][:20]:<20}{r['roi_pct']:>6}%"
|
||||
f"{'$'+format(r['total_bet'], ','):>13}{'$'+format(r['total_pnl'], ','):>13}"
|
||||
f"{'$'+format(r['avg_bet'], ','):>9}{r['pct_weeks_green']:>5}%"
|
||||
f"{r['profit_factor']:>6.1f}{r['weekly_sharpe']:>6.2f}{r['weeks']:>4}"
|
||||
f"{r['history_days']:>7}")
|
||||
print("-" * 100)
|
||||
print(f"{len(out)} copyable wallets · saved to copyable_77.csv")
|
||||
print(f" total staked across all: ${sum(r['total_bet'] for r in out):,}")
|
||||
print(f" median history: {statistics.median([r['history_days'] for r in out])} days")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user