copybot: feed reconciles my_pos -> bets so open_count matches the visible table
A position can land in my_pos without a bet record when its buy fill is drained in a context where _record_lag doesn't fire (e.g. a second market on the same event in one batch): cash-correct (open_count/deployed read my_pos) but INVISIBLE in the feed table — the header showed '2 open / $75 deployed' while only 1 bet rendered. write_feed now synthesizes a bet record from any open my_pos position lacking one, so the count always equals what's shown. Ledger was unaffected (drift -0.0) — display-only. my_pos entries now carry 'wallet' for proper name attribution on the synthesized record. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -486,7 +486,8 @@ class CopyTrader:
|
||||
else:
|
||||
self.state["my_pos"][token] = {
|
||||
"shares": res["filled_shares"], "cost": spent,
|
||||
"title": title, "outcome": outcome, "event": event}
|
||||
"title": title, "outcome": outcome, "event": event,
|
||||
"wallet": wallet} # attribution for the feed's my_pos->bets safety net
|
||||
tag = "[PAPER]" if not self.ex.live else "[LIVE]"
|
||||
self.alert(
|
||||
f"{kind} {label} — {tag} buy {res['filled_shares']:.1f} "
|
||||
|
||||
+22
@@ -499,6 +499,28 @@ class Copybot:
|
||||
st = self.engine.state
|
||||
bets = st.setdefault("bets", {})
|
||||
mp = st["my_pos"]
|
||||
# my_pos -> bets: a position can land in my_pos without a bet record when
|
||||
# its fill was drained in a context where _record_lag didn't fire (e.g. a
|
||||
# second market on the same event processed in one batch). It's cash-
|
||||
# correct (open_exposure/open_count read my_pos) but INVISIBLE in the feed
|
||||
# table, so the header showed "2 open" while the table showed 1. Synthesize
|
||||
# the missing record from my_pos so the counts always match what's shown.
|
||||
for tok, p in mp.items():
|
||||
b = bets.get(tok)
|
||||
if not b or b.get("status") != "open":
|
||||
bets[tok] = {
|
||||
"token": tok, "wallet": p.get("wallet", ""),
|
||||
"name": self.names.get((p.get("wallet") or "").lower())
|
||||
or (b or {}).get("name") or "?",
|
||||
"outcome": p.get("outcome"), "title": (p.get("title") or "")[:90],
|
||||
"their_price": None,
|
||||
"my_price": round(p["cost"] / p["shares"], 4) if p.get("shares") else None,
|
||||
"slippage_pct": None,
|
||||
"shares": round(p["shares"], 2), "cost": round(p["cost"], 2),
|
||||
"fee": (b or {}).get("fee", 0),
|
||||
"opened": (b or {}).get("opened") or int(time.time()), "status": "open",
|
||||
"exit_price": None, "pnl": None, "settled": None,
|
||||
}
|
||||
for tok, b in bets.items():
|
||||
if b["status"] == "open" and tok not in mp:
|
||||
b["status"] = "closed"
|
||||
|
||||
Reference in New Issue
Block a user