copybot: sold legs now realize on the bet record, not just cash
Mirror-exits were correct at book level (cash/fees) but invisible per bet: a fully mirror-sold bet went to 'closed' with pnl null, and a TRIMmed bet that later resolved subtracted its ORIGINAL cost+fee while ignoring the sold leg's proceeds — understating whole-bet pnl on the feed. * _drain_fills links every SELL fill to its bet record (sold_shares / sold_proceeds, net of taker fee) * write_feed: full mirror-sell -> pnl = sold_proceeds − cost − entry fee, plus exit_price; dashboard's existing 'mirror-sold with recorded P&L' branch picks it up * settle_resolved: pnl = resolution proceeds + sold leg − original cost − entry fee (pos.cost is trim-reduced; bet.cost is the original) Sandbox-tested: TRIM cash+record, TRIM->WIN whole-bet truth, full EXIT feed reconcile all agree to the cent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+20
-1
@@ -329,6 +329,14 @@ class Copybot:
|
||||
self.engine.state["fees_paid"] = self.engine.state.get("fees_paid", 0.0) + fee
|
||||
if f["side"] == "BUY":
|
||||
buys.append(f)
|
||||
else:
|
||||
# link the mirror-exit to its bet record so the sold leg shows
|
||||
# up in per-bet P&L (feed) — cash above is already correct
|
||||
b = self.engine.state.get("bets", {}).get(f["token"])
|
||||
if b:
|
||||
b["sold_shares"] = round(b.get("sold_shares", 0) + f["shares"], 2)
|
||||
b["sold_proceeds"] = round(b.get("sold_proceeds", 0)
|
||||
+ f["shares"] * f["price"] - fee, 2)
|
||||
ex.fills.clear()
|
||||
return buys
|
||||
|
||||
@@ -401,6 +409,12 @@ class Copybot:
|
||||
if b["status"] == "open" and tok not in mp:
|
||||
b["status"] = "closed"
|
||||
b["settled"] = b["settled"] or int(time.time())
|
||||
# fully mirror-sold: realize the sold leg on the bet record
|
||||
# (cash was already credited fill-by-fill in _drain_fills)
|
||||
if b.get("sold_proceeds") is not None and b.get("pnl") is None:
|
||||
b["pnl"] = round(b["sold_proceeds"] - b["cost"] - (b.get("fee") or 0), 2)
|
||||
if b.get("sold_shares"):
|
||||
b["exit_price"] = round(b["sold_proceeds"] / b["sold_shares"], 4)
|
||||
bank = self.cfg["bankroll_usd"]
|
||||
exp = self.engine.open_exposure()
|
||||
cash = st.get("cash", bank)
|
||||
@@ -594,7 +608,12 @@ class Copybot:
|
||||
proceeds = pos["shares"] * wp # redeem is fee-free on-chain
|
||||
b = self.engine.state.get("bets", {}).get(token)
|
||||
fee_in = (b or {}).get("fee") or 0 # entry taker fee, already off cash
|
||||
pnl = proceeds - pos["cost"] - fee_in
|
||||
# a TRIMmed bet realized part of itself pre-resolution: count the
|
||||
# sold leg and charge the ORIGINAL cost (pos["cost"] was reduced
|
||||
# proportionally at each trim), so per-bet pnl = whole-bet truth
|
||||
sold = (b or {}).get("sold_proceeds") or 0
|
||||
base_cost = b["cost"] if b else pos["cost"]
|
||||
pnl = proceeds + sold - base_cost - fee_in
|
||||
self.engine.state["cash"] += proceeds # recycle freed capital
|
||||
if b:
|
||||
b.update(status=("won" if wp >= 0.5 else "lost"),
|
||||
|
||||
Reference in New Issue
Block a user