008b3138cc
live/tape_sharps.py — insider.py's improbability z transplanted onto the 3-day tape (13.8M fills, ALL wallets, no survivorship bias): proxy-resolve tokens from terminal-VWAP convergence (0.97/0.03, 2h quiet, sibling veto), net held positions per wallet-token, z = (W - Σp)/sqrt(Σp(1-p)) over proxy-resolved held bets, then payouts.py chain-truth overlay on the shortlist (refunds count as neither). Copyable/algo split: discrete-entry conviction bettors (<= 6 fills/bet, <= 90 bets, med >= $50) vs continuous flow accounts that score huge but can't be mirrored at 3-17s lag. First run (tape through 2026-07-20 16:59): 2,360 wallets screened, 561 score z >= 2 with pnl > 0, 25 copyable candidates at z 4.0-5.5 — chain validation 742 payout vectors, ZERO proxy flips. Benchmark sanity: benched sharps (LSB1 +3.28, EdwardIN +2.38) land positive, benched losers negative. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
296 lines
13 KiB
Python
296 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
"""Tape-era sharp screen over the RTDS firehose (issue #2, first flow study).
|
|
|
|
The tape (rtds.duckdb `trades`, every CLOB fill since 2026-07-17) sees ALL
|
|
fills — including wallets that lose and vanish — so unlike the data-api
|
|
leaderboards there is NO survivorship bias to unwind ([[polymarket-smart-money]]:
|
|
displayed win rates ~92% collapse to ~50% once hidden losers count). The
|
|
screen is insider.py's improbability methodology transplanted onto tape:
|
|
|
|
1. RESOLVE FROM THE TAPE. A token is proxy-resolved when its final-30min
|
|
VWAP converged (>= 0.97 won / <= 0.03 lost) AND it stopped trading
|
|
QUIET_H before tape end AND no sibling token of the same condition
|
|
disagrees (two "winners" in one cond = still-live or bad data -> drop).
|
|
Chain truth (payouts.py) then overlays the shortlist's conditions, so
|
|
50/50 refunds and operator quirks can't survive to the report
|
|
([[polymarket-resolution-truth]]).
|
|
2. HELD NET POSITIONS, not trades. Per wallet-token: net = buys - sells;
|
|
only |net| that survives to the token's last tape print is a bet (a
|
|
scalper who round-trips out is not "holding" the resolution). Entry is
|
|
the buy VWAP; P&L = net * (payout - vwap).
|
|
3. IMPROBABILITY z. Over a wallet's proxy-resolved held bets: each entry
|
|
at price p wins with prob p under the null; z = (W - Σp)/sqrt(Σp(1-p)).
|
|
The tape's 3-day window means n is small — z >= 2 with n >= MIN_BETS
|
|
and positive P&L is a CANDIDATE, not a verdict; the bench forward
|
|
window is the verdict (bench review, issue #13).
|
|
4. CONTEXT COLUMNS for triage: niche mix (title keywords), notional,
|
|
price band, burst flag (wallet's first tape print < 48h before its
|
|
biggest bet — fresh-or-returning wallet swinging immediately, the
|
|
insider fingerprint), and the follow/bench overlap (excluded from
|
|
candidates, printed as benchmark rows so the screen can be sanity
|
|
checked against wallets we already believe in).
|
|
|
|
Outputs live/tape_sharps.json (full scored rows) and a console report.
|
|
|
|
python3 tape_sharps.py # screen + chain-validate top 25
|
|
python3 tape_sharps.py --no-chain # tape-only (no RPC)
|
|
python3 tape_sharps.py --min-bets 6 # loosen for exploration
|
|
"""
|
|
import argparse
|
|
import json
|
|
import math
|
|
import os
|
|
import time
|
|
|
|
import duckdb
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
DB = os.path.join(HERE, "rtds.duckdb")
|
|
OUT = os.path.join(HERE, "tape_sharps.json")
|
|
|
|
# proxy-resolution + bet thresholds (tunable; defaults are the 2026-07-20
|
|
# first-run calibration — see FINDINGS addendum in the issue)
|
|
WIN_T, LOSE_T = 0.97, 0.03 # final-30min VWAP convergence
|
|
QUIET_H = 2 # token must be quiet this long before tape end
|
|
MIN_NET_SH = 5.0 # net shares that count as a held bet
|
|
MIN_NET_USD = 20.0 # ...and its buy notional (dust bots out)
|
|
P_LO, P_HI = 0.05, 0.95 # entry band for the z math (insider.py clamps)
|
|
BURST_H = 48 # "fresh in tape" window for the burst flag
|
|
|
|
NICHES = [ # (label, TITLE ILIKE patterns) — first match wins
|
|
("esports", ["%lol:%", "%dota%", "%cs2%", "%csgo%", "%valorant%", "%esports%",
|
|
"% vs %game %", "%bilibili%", "%map %winner%"]),
|
|
("tennis", ["%tennis%", "%atp%", "%wta%", "%wimbledon%", "%open (m)%", "%set %winner%"]),
|
|
("sports", ["% vs. %", "% vs %", "% @ %", "%mlb%", "%nba%", "%nhl%", "%ufc%",
|
|
"%world cup%", "%f1%", "%grand prix%"]),
|
|
("crypto", ["%bitcoin%", "%btc%", "%ethereum%", "%eth %", "%solana%", "%xrp%",
|
|
"%price of%", "%above%on july%", "%above%on august%"]),
|
|
("politics", ["%election%", "%president%", "%senate%", "%governor%", "%mayor%",
|
|
"%nominee%", "%impeach%", "%tariff%", "%fed %", "%rate cut%"]),
|
|
("geo", ["%iran%", "%israel%", "%russia%", "%ukraine%", "%china%", "%taiwan%",
|
|
"%ceasefire%", "%strike%", "%nato%"]),
|
|
]
|
|
|
|
|
|
def known_wallets():
|
|
"""follow set + benches + the live bot itself -> {addr_lower: label}."""
|
|
out = {"0x455e252e45ee46d6c4cc1c8fadd3899d68f245a1": "OUR-BOT"}
|
|
try:
|
|
for w in json.load(open(os.path.join(HERE, "copybot.paper.json")))["wallets"]:
|
|
out[w["wallet"].lower()] = f"follow:{w.get('name', w['wallet'][:8])}"
|
|
except Exception:
|
|
pass
|
|
for fn, tag in (("watch_sharps.json", "bench"), ("watch_skilled.json", "skilled")):
|
|
try:
|
|
for w in json.load(open(os.path.join(HERE, fn))):
|
|
out.setdefault(w["wallet"].lower(), f"{tag}:{w.get('name', '?')}")
|
|
except Exception:
|
|
pass
|
|
return out
|
|
|
|
|
|
def niche_case():
|
|
whens = []
|
|
for label, pats in NICHES:
|
|
ors = " OR ".join(f"lower(title) LIKE '{p}'" for p in pats)
|
|
whens.append(f"WHEN ({ors}) THEN '{label}'")
|
|
return "CASE " + " ".join(whens) + " ELSE 'other' END"
|
|
|
|
|
|
def screen(db, min_bets):
|
|
"""One SQL pass: proxy-resolve tokens, net held positions, wallet rollup."""
|
|
t_end = db.execute("SELECT max(ts) FROM trades").fetchone()[0]
|
|
quiet = t_end - QUIET_H * 3600
|
|
db.execute(f"""
|
|
CREATE TEMP TABLE alltok AS
|
|
WITH last AS (
|
|
SELECT asset, any_value(cond) cond, max(ts) last_ts
|
|
FROM trades WHERE cond IS NOT NULL AND cond != '' GROUP BY asset
|
|
), term AS ( -- final-30min VWAP per token
|
|
SELECT t.asset,
|
|
sum(t.price * t.size) / nullif(sum(t.size), 0) term_vwap
|
|
FROM trades t JOIN last l ON t.asset = l.asset
|
|
WHERE t.ts >= l.last_ts - 1800 GROUP BY t.asset
|
|
)
|
|
SELECT l.asset, l.cond, l.last_ts, tm.term_vwap,
|
|
l.last_ts > {quiet} AS alive,
|
|
CASE WHEN tm.term_vwap >= {WIN_T} THEN 1.0
|
|
WHEN tm.term_vwap <= {LOSE_T} THEN 0.0 END AS payout
|
|
FROM last l JOIN term tm ON l.asset = tm.asset
|
|
""")
|
|
# cond veto: any sibling still trading (in-play comebacks exist), or two
|
|
# proxy-winners (multi-outcome not actually settled) -> drop the cond
|
|
db.execute("""
|
|
CREATE TEMP TABLE badcond AS
|
|
SELECT cond FROM alltok GROUP BY cond
|
|
HAVING bool_or(alive)
|
|
OR sum(CASE WHEN payout = 1.0 THEN 1 ELSE 0 END) > 1
|
|
""")
|
|
db.execute("""
|
|
CREATE TEMP TABLE tok AS
|
|
SELECT * FROM alltok WHERE NOT alive AND payout IS NOT NULL
|
|
""")
|
|
db.execute(f"""
|
|
CREATE TEMP TABLE bets AS
|
|
SELECT tr.wallet, tr.asset, any_value(tk.cond) cond,
|
|
any_value(tk.payout) payout,
|
|
any_value(tk.term_vwap) term_vwap,
|
|
sum(CASE WHEN tr.side = 'BUY' THEN tr.size ELSE -tr.size END) net,
|
|
sum(CASE WHEN tr.side = 'BUY' THEN tr.size * tr.price END)
|
|
/ nullif(sum(CASE WHEN tr.side = 'BUY' THEN tr.size END), 0) vwap,
|
|
sum(CASE WHEN tr.side = 'BUY' THEN tr.size * tr.price END) buy_usd,
|
|
count(*) n_fills,
|
|
any_value({niche_case()}) niche,
|
|
min(tr.ts) first_ts
|
|
FROM trades tr
|
|
JOIN tok tk ON tr.asset = tk.asset
|
|
WHERE tk.payout IS NOT NULL
|
|
AND tk.cond NOT IN (SELECT cond FROM badcond)
|
|
AND tr.ts <= tk.last_ts -- nothing after the last print
|
|
GROUP BY tr.wallet, tr.asset
|
|
HAVING net >= {MIN_NET_SH}
|
|
AND buy_usd >= {MIN_NET_USD}
|
|
AND vwap BETWEEN {P_LO} AND {P_HI}
|
|
""")
|
|
rows = db.execute(f"""
|
|
WITH w AS (
|
|
SELECT wallet,
|
|
count(*) n,
|
|
sum(CASE WHEN payout = 1.0 THEN 1 ELSE 0 END) wins,
|
|
sum(vwap) exp_wins,
|
|
sum(vwap * (1 - vwap)) var_sum,
|
|
sum(net * (payout - vwap)) pnl,
|
|
sum(buy_usd) notional,
|
|
avg(vwap) avg_p,
|
|
avg(n_fills) avg_fills,
|
|
median(buy_usd) med_bet,
|
|
mode(niche) top_niche,
|
|
count(DISTINCT niche) niches,
|
|
min(first_ts) first_seen
|
|
FROM bets GROUP BY wallet HAVING n >= {min_bets}
|
|
),
|
|
life AS (SELECT wallet, min(ts) tape_first FROM trades GROUP BY wallet)
|
|
SELECT w.*, life.tape_first FROM w JOIN life USING (wallet)
|
|
""").fetchall()
|
|
cols = ["wallet", "n", "wins", "exp_wins", "var_sum", "pnl", "notional",
|
|
"avg_p", "avg_fills", "med_bet", "top_niche", "niches",
|
|
"first_seen", "tape_first"]
|
|
out = []
|
|
for r in rows:
|
|
d = dict(zip(cols, r))
|
|
d["z"] = ((d["wins"] - d["exp_wins"]) / math.sqrt(d["var_sum"])
|
|
if d["var_sum"] > 0 else 0.0)
|
|
d["win_pct"] = 100.0 * d["wins"] / d["n"]
|
|
out.append(d)
|
|
return out, t_end
|
|
|
|
|
|
def chain_validate(cands, db):
|
|
"""Overlay payouts.truth on each candidate bet; rescore. Refund (0.5)
|
|
counts as neither win nor loss; unknown keeps the tape proxy."""
|
|
import payouts
|
|
conds = sorted({c for w in cands for c in w["_conds"]})
|
|
payouts.ensure(conds)
|
|
for w in cands:
|
|
n = wins = exp = var = pnl = 0.0
|
|
flip = 0
|
|
for (cond, asset, net, vwap, proxy) in w["_bets"]:
|
|
t = payouts.truth(cond, asset)
|
|
pay = proxy if t is None else t
|
|
if pay == 0.5: # refund: stake back, no win/no loss
|
|
pnl += 0.0
|
|
continue
|
|
if t is not None and t != proxy:
|
|
flip += 1
|
|
n += 1
|
|
wins += pay
|
|
exp += vwap
|
|
var += vwap * (1 - vwap)
|
|
pnl += net * (pay - vwap)
|
|
w.update(n_chain=int(n), wins_chain=int(wins), pnl_chain=round(pnl, 2),
|
|
flips=flip,
|
|
z_chain=round((wins - exp) / math.sqrt(var), 2) if var > 0 else 0.0)
|
|
return cands
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--min-bets", type=int, default=8)
|
|
ap.add_argument("--top", type=int, default=25, help="chain-validate this many")
|
|
ap.add_argument("--no-chain", action="store_true")
|
|
ap.add_argument("--max-bets", type=int, default=90, help="copyable cadence cap")
|
|
ap.add_argument("--max-fills", type=float, default=6.0, help="avg fills/bet cap")
|
|
ap.add_argument("--min-med-usd", type=float, default=50.0, help="median bet floor")
|
|
args = ap.parse_args()
|
|
|
|
db = duckdb.connect(DB, read_only=True)
|
|
known = known_wallets()
|
|
scored, t_end = screen(db, args.min_bets)
|
|
scored.sort(key=lambda d: -d["z"])
|
|
|
|
# COPYABLE means the bot could actually mirror it at 3-17s lag: discrete
|
|
# entries (few fills per bet, not continuous flow), human cadence (<=
|
|
# max-bets held bets in the 3-day tape), conviction-sized (median bet
|
|
# clears the $25 follow floor with margin). Everything else that scores
|
|
# is ALGO FLOW — real edge, uncopyable execution (they ARE the crater).
|
|
def copyable(d):
|
|
return (d["avg_fills"] <= args.max_fills and d["n"] <= args.max_bets
|
|
and d["med_bet"] >= args.min_med_usd)
|
|
|
|
fresh = [d for d in scored if d["z"] >= 2.0 and d["pnl"] > 0
|
|
and d["wallet"].lower() not in known]
|
|
interesting = [d for d in fresh if copyable(d)][:args.top]
|
|
algos = [d for d in fresh if not copyable(d)][:8]
|
|
for d in interesting:
|
|
rows = db.execute("""
|
|
SELECT cond, asset, net, vwap, payout FROM bets WHERE wallet = ?
|
|
""", [d["wallet"]]).fetchall()
|
|
d["_conds"] = [r[0] for r in rows]
|
|
d["_bets"] = rows
|
|
if interesting and not args.no_chain:
|
|
chain_validate(interesting, db)
|
|
for d in interesting:
|
|
d.pop("_conds", None); d.pop("_bets", None)
|
|
|
|
bench = [d for d in scored if d["wallet"].lower() in known]
|
|
stamp = time.strftime("%Y-%m-%d %H:%M UTC", time.gmtime(t_end))
|
|
|
|
def fmt(d):
|
|
age_h = (t_end - d["tape_first"]) / 3600
|
|
burst = "BURST" if age_h <= BURST_H else f"{age_h:.0f}h"
|
|
chain = (f" · chain z={d['z_chain']} {d['wins_chain']}/{d['n_chain']} "
|
|
f"${d['pnl_chain']:+,.0f} ({d['flips']} flips)"
|
|
if "z_chain" in d else "")
|
|
return (f"{d['wallet'][:10]}… z={d['z']:+.2f} {d['wins']}/{d['n']} "
|
|
f"({d['win_pct']:.0f}%) avg_p {d['avg_p']:.2f} "
|
|
f"med ${d['med_bet']:,.0f} f/b {d['avg_fills']:.1f} "
|
|
f"pnl ${d['pnl']:+,.0f} vol ${d['notional']:,.0f} "
|
|
f"{d['top_niche']}({d['niches']}) {burst}{chain}")
|
|
|
|
print(f"tape through {stamp} · {len(scored)} wallets with >= "
|
|
f"{args.min_bets} proxy-resolved held bets\n")
|
|
print(f"— COPYABLE candidates (z >= 2, pnl > 0, <= {args.max_bets} bets, "
|
|
f"<= {args.max_fills:.0f} fills/bet, med >= ${args.min_med_usd:.0f}) —")
|
|
for d in interesting:
|
|
print(fmt(d))
|
|
print(f"\n— algo flow (scores, but uncopyable execution; {len(algos)} of "
|
|
f"{len(fresh) - len(interesting)}) —")
|
|
for d in algos:
|
|
print(fmt(d))
|
|
print("\n— benchmark: known wallets through the same screen —")
|
|
for d in sorted(bench, key=lambda x: -x["z"])[:15]:
|
|
print(f"[{known[d['wallet'].lower()]}] {fmt(d)}")
|
|
|
|
json.dump({"tape_end": t_end, "params": {
|
|
"min_bets": args.min_bets, "win_t": WIN_T, "lose_t": LOSE_T,
|
|
"quiet_h": QUIET_H, "min_net_sh": MIN_NET_SH,
|
|
"min_net_usd": MIN_NET_USD, "p_band": [P_LO, P_HI]},
|
|
"candidates": interesting, "benchmark": bench,
|
|
"screened": len(scored)},
|
|
open(OUT, "w"), indent=1, default=float)
|
|
print(f"\nwrote {OUT}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|