From efb33e0e8b6a611a5f8327f2a4dd86f72bc27a4d Mon Sep 17 00:00:00 2001 From: jaxperro Date: Thu, 23 Jul 2026 13:15:26 -0400 Subject: [PATCH] =?UTF-8?q?research:=20sell=5Fmirror=5Fstudy=20=E2=80=94?= =?UTF-8?q?=20reproducible=20basis=20for=20the=20hold-through=20pre-regist?= =?UTF-8?q?ration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- research/sell_mirror_study.py | 85 +++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 research/sell_mirror_study.py diff --git a/research/sell_mirror_study.py b/research/sell_mirror_study.py new file mode 100644 index 00000000..5efc8fe9 --- /dev/null +++ b/research/sell_mirror_study.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""Sell-mirror counterfactual (2026-07-23, basis for the hold-through +pre-registration): for every mirrored SELL in both books, chain-true +compare the exit against holding to resolution. Their entries are the +copied signal; are their exits information or just their bankroll ops? + +Per sell: delta = (chain_payout − sell_price) × shares. delta>0 = the exit +gave up value (token resolved above our sell); delta<0 = the exit saved us. +Reported pooled and per-wallet (sells attributed to the token's buy-side +wallet). Refunds excluded. Rerunnable any time; grading coverage grows +with the tape/payout cache.""" +import json +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import tape # noqa: E402 +import forward as fwd # noqa: E402 + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +def main(): + tok2name = {} + sells = [] + for path, book in ((os.path.join(ROOT, "copybot_fills.live.jsonl"), "live"), + (os.path.join(ROOT, "copybot_fills.jsonl"), "paper")): + for ln in open(path): + r = json.loads(ln) + if r.get("untracked"): + continue + if r.get("side") == "SELL": + r["_book"] = book + sells.append(r) + elif r.get("name"): + tok2name[str(r["token"])] = r["name"] + db = tape.connect() + tape.build_resolved(db) + pays = fwd.payouts_for(db, [str(s["token"]) for s in sells]) + for book in ("live", "paper"): + ss = [s for s in sells if s["_book"] == book] + n = unk = refund = right = wrong = 0 + saved = given = 0.0 + for s in ss: + p = pays.get(str(s["token"])) + if p is None: + unk += 1 + continue + if p == 0.5: + refund += 1 + continue + n += 1 + delta = (p - s["price"]) * s.get("shares", 0) + if delta > 0: + given += delta + wrong += 1 + else: + saved -= delta + right += 1 + if n: + print(f"[{book}] {len(ss)} sells · {n} graded ({unk} unresolved, " + f"{refund} refunds)") + print(f" exit RIGHT: {right} (saved ${saved:.2f}) · exit WRONG: " + f"{wrong} (gave up ${given:.2f}) · net of mirroring " + f"${saved-given:+.2f}") + agg = {} + for s in sells: + p = pays.get(str(s["token"])) + if p is None or p == 0.5: + continue + nm = tok2name.get(str(s["token"]), "?") + a = agg.setdefault(nm, dict(n=0, net=0.0, wrong=0)) + a["n"] += 1 + delta = (p - s["price"]) * s.get("shares", 0) + a["net"] -= delta + a["wrong"] += delta > 0 + print("\nper-wallet (both books):") + for nm, a in sorted(agg.items(), key=lambda x: -x[1]["n"]): + print(f" {nm:<18} n={a['n']:>3} · exit-wrong {a['wrong']:>3} " + f"({100*a['wrong']/a['n']:.0f}%) · net of mirroring " + f"${a['net']:+8.2f}") + + +if __name__ == "__main__": + main()