From 8b91893fc9eeec3b0a10f774bb336bab11717c66 Mon Sep 17 00:00:00 2001 From: jaxperro Date: Thu, 9 Jul 2026 14:04:31 -0400 Subject: [PATCH] missed bets settle at the sharp's EXIT when they sold pre-resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The missed ledger valued everything hold-to-resolution — the wrong counterfactual for a mirror strategy (Kruto's 3c Hive entry: he banked 16x selling at 48c; the paper book mirrored for +$722; the live book's missed row would have shown the map result instead). Three affirmative facts before settling (same as reconcile_exits), their reconstructed exit price, entry+exit taker fees. Also: the reconcile miss reason is now honest — 'not copied in the detection window (reconciled)'. Co-Authored-By: Claude Fable 5 --- copybot.py | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/copybot.py b/copybot.py index ee965a8d..b2975340 100644 --- a/copybot.py +++ b/copybot.py @@ -1223,10 +1223,51 @@ class Copybot: self.engine.record_miss( w, tok, p.get("conditionId"), p.get("title") or "", p.get("outcome") or "", trade["price"], want, - "bot offline (entered while down)") + "not copied in the detection window (reconciled)") missed_toks.add(tok) log(f"reconcile: {name} entered {(p.get('title') or '?')[:42]} " f"while we weren't listening — recorded as missed") + # exit-aware missed settling: a missed bet whose sharp FULLY + # EXITED pre-resolution settles at THEIR exit — the mirror + # counterfactual — instead of riding to resolution (Kruto's 3c + # Hive entry 2026-07-09: he banked 16x selling at 48c; the + # hold-to-resolution valuation would have shown the map result + # instead). Same three affirmative facts as reconcile_exits — + # silence never reads as an exit. Entry+exit taker fees charged, + # like every mirrored sell. + for m in st.get("missed", []): + if (m.get("status") != "open" or not m.get("cond") + or not m.get("wallet") or not m.get("token")): + continue + ps = sm.get_json("/positions", {"user": m["wallet"], "market": m["cond"], + "limit": 10, "sizeThreshold": 0}) + if ps is None: + continue + if any(str(p.get("asset")) == str(m["token"]) and (p.get("size") or 0) > 0 + for p in ps): + continue # still held — resolution path waits + cps = sm.get_json("/closed-positions", {"user": m["wallet"], + "market": m["cond"], "limit": 10}) + if cps is None: + continue + row = next((p for p in cps if str(p.get("asset")) == str(m["token"])), None) + if row is None: + continue + mk = _market(m["cond"]) + if not mk or mk.get("closed"): + continue # resolved -> chain-truth settle path + tb = row.get("totalBought") or 0 + xp = (row.get("avgPrice") or 0) + ((row.get("realizedPnl") or 0) / tb if tb else 0) + p = m.get("price") or 0.5 + sh = m["stake"] / max(p, 0.001) + fee_in = taker_fee(sh, p, self.fee_rate) + fee_out = taker_fee(sh, xp, self.fee_rate) + m.update(status="sold", exit_price=round(xp, 4), + pnl=round(sh * xp - m["stake"] - fee_in - fee_out, 2), + settled=int(time.time())) + log(f"missed-settle: {m.get('name') or m['wallet'][:8]} exited " + f"{(m.get('title') or '?')[:38]} @ {xp:.3f} — " + f"hypothetical ${m['pnl']:+.2f} (sold)") self.engine.persist() def settle_resolved(self):