mirror of
https://github.com/jaxperro/winning-wallet-finder.git
synced 2026-07-28 00:07:47 +00:00
live bot: record + settle missed bets, publish in feed
Engine records every blocked OPEN (no free cash, event cap, price guard) with its would-be stake; settle_resolved marks them won/lost at CLOB resolution with hypothetical P&L (fee-inclusive) - the live counterpart of the backtest's Missed table. Feed gains missed[] + missed_pnl. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+26
-2
@@ -278,6 +278,21 @@ class CopyTrader:
|
|||||||
frac *= self.DD_FACTOR
|
frac *= self.DD_FACTOR
|
||||||
return frac * eq
|
return frac * eq
|
||||||
|
|
||||||
|
def record_miss(self, wallet, token, cond, title, outcome, price, want, reason):
|
||||||
|
"""A bet the strategy WOULD have copied but the book couldn't take —
|
||||||
|
free cash gone, event cap, price drifted past the guard. Kept in state so
|
||||||
|
the live feed can show missed bets and (once the market resolves) their
|
||||||
|
would-be P&L — the live counterpart of the backtest's Missed table."""
|
||||||
|
missed = self.state.setdefault("missed", [])
|
||||||
|
if any(m["token"] == token and m["status"] == "open" for m in missed):
|
||||||
|
return # already recorded while open
|
||||||
|
missed.append({"ts": int(time.time()), "wallet": wallet, "token": token,
|
||||||
|
"cond": cond, "title": title, "outcome": outcome,
|
||||||
|
"price": round(price or 0, 4), "stake": round(want, 2),
|
||||||
|
"reason": reason, "status": "open", "pnl": None,
|
||||||
|
"settled": None})
|
||||||
|
del missed[:-200] # keep the recent 200
|
||||||
|
|
||||||
def persist(self):
|
def persist(self):
|
||||||
self.state["seen_tx"] = list(self.seen)[-5000:]
|
self.state["seen_tx"] = list(self.seen)[-5000:]
|
||||||
save_json(self.state_path, self.state)
|
save_json(self.state_path, self.state)
|
||||||
@@ -330,7 +345,8 @@ class CopyTrader:
|
|||||||
|
|
||||||
if side == "BUY":
|
if side == "BUY":
|
||||||
self._handle_their_buy(wallet, token, their_size, their_price,
|
self._handle_their_buy(wallet, token, their_size, their_price,
|
||||||
label, title, outcome, event=event_key(t))
|
label, title, outcome, event=event_key(t),
|
||||||
|
cond=t.get("conditionId"))
|
||||||
their_book[token] = their_prev + their_size
|
their_book[token] = their_prev + their_size
|
||||||
elif side == "SELL":
|
elif side == "SELL":
|
||||||
self._handle_their_sell(token, their_size, their_prev, label)
|
self._handle_their_sell(token, their_size, their_prev, label)
|
||||||
@@ -352,7 +368,7 @@ class CopyTrader:
|
|||||||
return drift <= self.cfg["price_guard_pct"]
|
return drift <= self.cfg["price_guard_pct"]
|
||||||
|
|
||||||
def _handle_their_buy(self, wallet, token, their_size, their_price,
|
def _handle_their_buy(self, wallet, token, their_size, their_price,
|
||||||
label, title, outcome, event=None):
|
label, title, outcome, event=None, cond=None):
|
||||||
mine = self.state["my_pos"].get(token)
|
mine = self.state["my_pos"].get(token)
|
||||||
is_add = mine is not None
|
is_add = mine is not None
|
||||||
# don't backfill: never open a position they already held when we
|
# don't backfill: never open a position they already held when we
|
||||||
@@ -371,6 +387,8 @@ class CopyTrader:
|
|||||||
if held >= cap:
|
if held >= cap:
|
||||||
self.log(f"BUY {label} — skip (already {held} positions on this "
|
self.log(f"BUY {label} — skip (already {held} positions on this "
|
||||||
f"event, cap {cap})")
|
f"event, cap {cap})")
|
||||||
|
self.record_miss(wallet, token, cond, title, outcome, their_price,
|
||||||
|
self.stake_usd(), f"event cap ({held} held)")
|
||||||
return
|
return
|
||||||
|
|
||||||
price = self._live_price(token, "buy")
|
price = self._live_price(token, "buy")
|
||||||
@@ -379,6 +397,9 @@ class CopyTrader:
|
|||||||
if not self._price_guard_ok(price, their_price):
|
if not self._price_guard_ok(price, their_price):
|
||||||
self.log(f"BUY {label} — skip (price {price:.3f} vs their "
|
self.log(f"BUY {label} — skip (price {price:.3f} vs their "
|
||||||
f"{their_price:.3f}, >{self.cfg['price_guard_pct']:.0%})")
|
f"{their_price:.3f}, >{self.cfg['price_guard_pct']:.0%})")
|
||||||
|
self.record_miss(wallet, token, cond, title, outcome, price,
|
||||||
|
self.stake_usd(),
|
||||||
|
f"price moved {their_price:.2f}→{price:.2f}")
|
||||||
return
|
return
|
||||||
|
|
||||||
if is_add:
|
if is_add:
|
||||||
@@ -396,6 +417,9 @@ class CopyTrader:
|
|||||||
allowed, reason = self.gate_buy(want_usd, price, pos_cost)
|
allowed, reason = self.gate_buy(want_usd, price, pos_cost)
|
||||||
if reason:
|
if reason:
|
||||||
self.log(f"{kind} {label} — skip ({reason})")
|
self.log(f"{kind} {label} — skip ({reason})")
|
||||||
|
if not is_add: # a blocked OPEN is a missed bet
|
||||||
|
self.record_miss(wallet, token, cond, title, outcome, price,
|
||||||
|
want_usd, reason)
|
||||||
return
|
return
|
||||||
shares = allowed / price
|
shares = allowed / price
|
||||||
res = self.ex.buy(token, shares, price, {"title": title})
|
res = self.ex.buy(token, shares, price, {"title": title})
|
||||||
|
|||||||
+23
@@ -375,6 +375,10 @@ class Copybot:
|
|||||||
exp = self.engine.open_exposure()
|
exp = self.engine.open_exposure()
|
||||||
cash = st.get("cash", bank)
|
cash = st.get("cash", bank)
|
||||||
lag = st.get("lag", {})
|
lag = st.get("lag", {})
|
||||||
|
missed = st.get("missed", [])
|
||||||
|
for m in missed: # display names for the feed
|
||||||
|
m["name"] = self.names.get((m.get("wallet") or "").lower(),
|
||||||
|
(m.get("wallet") or "")[:10])
|
||||||
feed = {
|
feed = {
|
||||||
"mode": "live" if self.engine.ex.live else "paper",
|
"mode": "live" if self.engine.ex.live else "paper",
|
||||||
"bankroll": bank, "stake": round(self.engine.stake_usd(), 2),
|
"bankroll": bank, "stake": round(self.engine.stake_usd(), 2),
|
||||||
@@ -392,6 +396,11 @@ class Copybot:
|
|||||||
"bets": sorted(bets.values(),
|
"bets": sorted(bets.values(),
|
||||||
key=lambda b: b.get("settled") or b.get("opened") or 0,
|
key=lambda b: b.get("settled") or b.get("opened") or 0,
|
||||||
reverse=True)[:100],
|
reverse=True)[:100],
|
||||||
|
"missed": sorted(missed,
|
||||||
|
key=lambda m: m.get("settled") or m.get("ts") or 0,
|
||||||
|
reverse=True)[:60],
|
||||||
|
"missed_pnl": round(sum(m["pnl"] for m in missed
|
||||||
|
if m.get("pnl") is not None), 2),
|
||||||
}
|
}
|
||||||
# only (re)write — and so only commit — when the meaningful content changed,
|
# only (re)write — and so only commit — when the meaningful content changed,
|
||||||
# not on every poll. The "updated" stamp advances only on real change, so the
|
# not on every poll. The "updated" stamp advances only on real change, so the
|
||||||
@@ -566,6 +575,20 @@ class Copybot:
|
|||||||
discord_text=(f"🏁 **SETTLE** {tag}\n{label}\n"
|
discord_text=(f"🏁 **SETTLE** {tag}\n{label}\n"
|
||||||
f"${pos['cost']:.2f} cost -> ${proceeds:.2f} "
|
f"${pos['cost']:.2f} cost -> ${proceeds:.2f} "
|
||||||
f"= **${pnl:+.2f}**"))
|
f"= **${pnl:+.2f}**"))
|
||||||
|
# settle MISSED bets hypothetically: what the skipped stake would have
|
||||||
|
# returned (entry fee included; redeem free) — the live counterpart of
|
||||||
|
# the backtest's Missed P&L, "the cost of a small bankroll".
|
||||||
|
for m in self.engine.state.get("missed", []):
|
||||||
|
if m.get("status") != "open" or not m.get("cond"):
|
||||||
|
continue
|
||||||
|
wp = resolution_price(m["token"], m["cond"], m.get("outcome"))
|
||||||
|
if wp is None:
|
||||||
|
continue
|
||||||
|
p = m.get("price") or 0.5
|
||||||
|
fee = taker_fee(m["stake"] / p, p, self.fee_rate)
|
||||||
|
pnl = (m["stake"] / p) * wp - m["stake"] - fee
|
||||||
|
m.update(status=("won" if wp >= 0.5 else "lost"),
|
||||||
|
pnl=round(pnl, 2), settled=int(time.time()))
|
||||||
self.engine.persist()
|
self.engine.persist()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user