#!/usr/bin/env python3 """Did any Alpha Arena season 1 model beat 2,048 coin-flip monkeys? Served at https://oaia.fun/recompute_alpha_arena.py. Python 3, standard library only. THE METHOD, fixed in this text before any percentile was computed: Data. Season 1 of Nof1's Alpha Arena gave six AI models $10,000 each to trade crypto perpetuals on Hyperliquid from public wallets. `--fetch` downloads every fill and funding payment of those wallets from Hyperliquid's own info API, plus one price per coin at the bell from Binance, into alpha_arena_s1.json. The window runs from 2025-10-17 22:00Z, the earliest fills the API returns, to the bell, 2025-11-03 22:00Z (5 p.m. EST). Fills after the bell are ignored, spot dust conversions are dropped. Position. Per model and coin, one position runs from flat to flat, with its adds and partial closes inside it. Its result r is its realized profit (the exchange's closedPnl), plus its funding, plus, if still open at the bell, the move from its average entry to the Binance price at the bell. Fees stay out of r, because a monkey pays the same fees. Monkeys. Each model gets 2,048 monkeys. A monkey holds every one of the model's positions, same moments and same sizes, and flips a fair coin for the side. The other side of a position makes -r. The coins are dealt in the order the positions opened, from a generator seeded with sha256('OAIA|alpha-arena-s1|'). Percentile: the share of monkeys the model finished above, ties counted half. 50 is a coin flip. The bar, the same as on /ai-traders: beating all but at most one of the 2,048 (p < 0.001), or losing to all but at most one. Inside the bar a model cannot be told from luck at this sample size, which is never the same as having no skill. Second reading, fixed now too: positions opened on the same UTC day share one coin, seeded with sha256('OAIA|alpha-arena-s1-day|'), because a model's positions on one day tend to be one bet. The verdict rests on the first reading; both are printed. Control. 10,000 + sum(r) - fees rebuilds each model's account at the bell, compared with Nof1's published final value. A row more than 5% off says so on the page. AMENDED before this version's first scored run. The first version (Internet Archive capture 20261106071157) failed its own control, accounts up to 27% off. The method above did not change: 1. One order's fills share a millisecond and their trade ids are not in fill order. The first version sorted by trade id, which cut positions in the wrong places. Now the position is walked within a millisecond, and a fill that does not continue the position stops the script. 2. Some wallets' funding comes as one row per coin per day, stamped 00:00. Such a row now goes to the position on that coin open longest that day, and rows from 2025-10-17 count. 3. The control now reads the figures of the original season recap. A later reading of nof1.ai, recorded by TradeRank on 2026-08-06, agrees on two models and differs on four; both are kept. The first version's wrong output is kept in alpha_arena_first_run.json. Usage, in a folder holding alpha_arena_s1.json and alpha_arena.json: python3 recompute_alpha_arena.py rebuild every row; exit code 0 means all match python3 recompute_alpha_arena.py --fetch replace alpha_arena_s1.json with a fresh download python3 recompute_alpha_arena.py --demo the controls, on made-up trades """ import hashlib import json import random import statistics import sys import urllib.request COHORT = 2048 T0 = 1760738400000 # 2025-10-17 22:00Z BELL = 1762207200000 # 2025-11-03 22:00Z START = 10000.0 DAY = 86400000 INFO = "https://api.hyperliquid.xyz/info" KLINES = "https://data-api.binance.vision/api/v3/klines?symbol={}USDT&interval=1m&startTime={}&limit=1" COINS = ("BTC", "ETH", "SOL", "BNB", "DOGE", "XRP") # The wallets the organizers listed for season 1, the final account values from the # original season recap, and nof1.ai as recorded by TradeRank from its 2026-08-06 snapshot. WALLETS = { "Qwen 3 Max": "0x7a8fd8bba33e37361ca6b0cb4518a44681bad2f3", "DeepSeek V3.1": "0xc20ac4dc4188660cbf555448af52694ca62b0734", "Claude Sonnet 4.5": "0x59fa085d106541a834017b97060bcbbb0aa82869", "Gemini 2.5 Pro": "0x1b7a7d099a670256207a30dd0ae13d35f278010f", "Grok 4": "0x56d652e62998251b56c8398fb11fcfe464c08f84", "GPT-5": "0x67293d914eafb26878534571add81f6bd2d9fe06", } REPORTED = {"Qwen 3 Max": 12231, "DeepSeek V3.1": 10489, "Claude Sonnet 4.5": 6919, "Gemini 2.5 Pro": 4329, "Grok 4": 5470, "GPT-5": 3734} LATER_READING = {"Qwen 3 Max": 12231, "DeepSeek V3.1": 10489, "Claude Sonnet 4.5": 5799, "Gemini 2.5 Pro": 5445, "Grok 4": 4208, "GPT-5": 4126} def _info(body): request = urllib.request.Request(INFO, data=json.dumps(body).encode(), headers={"Content-Type": "application/json"}) with urllib.request.urlopen(request, timeout=30) as reply: return json.load(reply) def _pages(kind, wallet, key): """Every row of one endpoint in the window; the API answers a page at a time.""" rows, since = {}, T0 - T0 % DAY while True: page = _info({"type": kind, "user": wallet, "startTime": since, "endTime": BELL}) if not page: return [rows[k] for k in sorted(rows)] rows.update((key(row), row) for row in page) newest = max(row["time"] for row in page) if newest == since: return [rows[k] for k in sorted(rows)] since = newest def fetch(): data = {"t0": T0, "bell": BELL, "start": START, "reported": REPORTED, "later_reading": LATER_READING, "wallets": WALLETS, "marks": {}, "fills": {}, "funding": {}} for coin in COINS: with urllib.request.urlopen(KLINES.format(coin, BELL), timeout=30) as reply: data["marks"][coin] = float(json.load(reply)[0][1]) for model, wallet in WALLETS.items(): fills = _pages("userFillsByTime", wallet, lambda f: (f["time"], f["tid"])) data["fills"][model] = [ [f["time"], f["tid"], f["coin"], f["side"], float(f["sz"]), float(f["px"]), float(f["startPosition"]), float(f["closedPnl"]), float(f["fee"])] for f in fills] funding = _pages("userFunding", wallet, lambda f: (f["time"], f["delta"]["coin"])) data["funding"][model] = [[f["time"], f["delta"]["coin"], float(f["delta"]["usdc"])] for f in funding] print(f"{model}: {len(fills)} fills, {len(funding)} funding rows") with open("alpha_arena_s1.json", "w", encoding="utf-8") as out: json.dump(data, out, separators=(",", ":")) def positions(fills, funding, marks, t0=T0, bell=BELL): """Flat to flat, per coin, in the order the fills happened.""" held, done, dropped, walked = {}, [], 0, {} # One order's fills share a millisecond and their trade ids are not in fill order, so # within a millisecond walk the position: buys climb it, sells descend it. for t, _, coin, side, size, price, before, closed, fee in sorted( fills, key=lambda f: (f[0], f[2], f[6] if f[3] == "B" else -f[6])): if coin not in marks: dropped += 1 continue if not t0 <= t <= bell: continue if abs(walked.get(coin, before) - before) > 1e-6: raise ValueError(f"{coin} fill at {t} does not continue the position") walked[coin] = round(before + (size if side == "B" else -size), 8) p = held.get(coin) if p is None: p = held[coin] = {"coin": coin, "opened": t0 if before else t, "seen": t, "size": before, "entry": price, "carried": bool(before), "realized": 0.0, "funding": 0.0, "fees": 0.0, "closed": None, "fills": 0} after = round(before + (size if side == "B" else -size), 8) if abs(after) > abs(before): p["entry"] = (p["entry"] * abs(before) + price * size) / abs(after) if before and after and (before > 0) != (after > 0): raise ValueError(f"{coin} crossed zero inside one fill at {t}") p["realized"] += closed p["fees"] += fee p["fills"] += 1 p["size"] = after if abs(after) < 1e-9: p["closed"] = t done.append(held.pop(coin)) still_open = list(held.values()) for p in still_open: p["unrealized"] = (marks[p["coin"]] - p["entry"]) * p["size"] every = sorted(done + still_open, key=lambda p: (p["opened"], p["seen"], p["coin"])) unassigned = 0.0 for t, coin, usdc in funding: if not t0 - t0 % DAY <= t <= bell: continue here = [p for p in every if p["coin"] == coin] inside = [p for p in here if p["opened"] <= t <= (p["closed"] or bell)] if not inside: # A daily row stamped 00:00: the position on this coin open longest that day. overlap = lambda p: min(p["closed"] or bell, t + DAY) - max(p["opened"], t) inside = [p for p in sorted(here, key=overlap, reverse=True) if overlap(p) > 0][:1] if inside: inside[0]["funding"] += usdc else: unassigned += usdc for p in every: p["r"] = p["realized"] + p["funding"] + p.get("unrealized", 0.0) return every, unassigned, dropped def _generator(tag, model): digest = hashlib.sha256(f"OAIA|{tag}|{model}".encode("utf-8")).digest() return random.Random(int.from_bytes(digest[:8], "big")) def score(model, rows): """Both readings for one model: below and ties against the cohort, plus the monkeys' median.""" rets = [p["r"] for p in rows] own = sum(rets) rng = _generator("alpha-arena-s1", model) below = ties = 0 totals = [] for _ in range(COHORT): monkey = sum((1.0 if rng.random() < 0.5 else -1.0) * r for r in rets) totals.append(monkey) below += monkey < own ties += monkey == own days = sorted({p["opened"] // DAY for p in rows}) rng = _generator("alpha-arena-s1-day", model) day_below = day_ties = 0 for _ in range(COHORT): side = {day: (1.0 if rng.random() < 0.5 else -1.0) for day in days} monkey = sum(side[p["opened"] // DAY] * p["r"] for p in rows) day_below += monkey < own day_ties += monkey == own return {"below": below, "ties": ties, "percentile": round(100 * (below + 0.5 * ties) / COHORT, 1), "day_below": day_below, "day_ties": day_ties, "day_percentile": round(100 * (day_below + 0.5 * day_ties) / COHORT, 1), "days": len(days), "own": round(own, 2), "median_monkey": round(statistics.median(totals), 2)} def rebuild(data): rows = [] excluded = 0 for model in data["wallets"]: every, unassigned, dropped = positions(data["fills"][model], data["funding"][model], data["marks"], data["t0"], data["bell"]) excluded += dropped fees = sum(p["fees"] for p in every) s = score(model, every) final = data["start"] + s["own"] - fees + unassigned total_abs = sum(abs(p["r"]) for p in every) rows.append({ "model": model, "wallet": data["wallets"][model], "reported_final": data["reported"][model], "later_reading": data["later_reading"][model], "rebuilt_final": round(final, 2), "off_pct": round(100 * (final - data["reported"][model]) / data["reported"][model], 1), "positions": len(every), "fills": sum(p["fills"] for p in every), "fees": round(fees, 2), "funding": round(sum(p["funding"] for p in every), 2), "unassigned_funding": round(unassigned, 2), "carried": sum(p["carried"] for p in every), "open_at_bell": sum(p["closed"] is None for p in every), "top_share": round(max(abs(p["r"]) for p in every) / total_abs, 3), "median_monkey_final": round(data["start"] + s["median_monkey"] - fees + unassigned, 2), **s}) return {"season": "Alpha Arena season 1", "cohort": COHORT, "t0": data["t0"], "bell": data["bell"], "bar_monkeys": 1, "dropped_spot_fills": excluded, "rows": rows} def demo(): """Made-up trades through the same code: random sides sit near 50, planted skill near 100.""" rng = random.Random(20261106) def world(skill): fills, price, t = [], 100.0, T0 + 1000 for i in range(200): move = rng.gauss(0, 1.0) side = (move > 0) == (rng.random() < 0.5 + skill) open_side, close_side = ("B", "A") if side else ("A", "B") before = 1.0 if side else -1.0 fills.append([t, i, "BTC", open_side, 1.0, price, 0.0, 0.0, 0.1]) pnl = move if side else -move fills.append([t + 1, 1000 + i, "BTC", close_side, 1.0, price + move, before, pnl, 0.1]) t += 3600000 price += move return positions(fills, [], {"BTC": price})[0] flat = [score(f"scrambled-{i}", world(0.0))["percentile"] for i in range(20)] planted = [score(f"planted-{i}", world(0.25))["percentile"] for i in range(5)] print("scrambled sides, percentiles:", flat, "mean", round(statistics.mean(flat), 1)) print("planted skill, percentiles:", planted) return 0 if 25 <= statistics.mean(flat) <= 75 and min(planted) > 95 else 1 def main(): if "--demo" in sys.argv: return demo() if "--fetch" in sys.argv: fetch() return 0 with open("alpha_arena_s1.json", encoding="utf-8") as f: result = rebuild(json.load(f)) if "--write" in sys.argv: with open(sys.argv[sys.argv.index("--write") + 1], "w", encoding="utf-8") as out: json.dump(result, out, indent=1) out.write("\n") else: with open("alpha_arena.json", encoding="utf-8") as f: published = json.load(f) if result != published: print("rebuilt rows differ from alpha_arena.json") return 1 for row in result["rows"]: print(f"{row['model']}: {row['positions']} positions, percentile {row['percentile']}, " f"same-day coin {row['day_percentile']}, account {row['rebuilt_final']:,.0f} " f"against {row['reported_final']:,} published") print(f"{len(result['rows'])} rows, 0 mismatches") return 0 if __name__ == "__main__": sys.exit(main())