fix: auto-anchor book cash to chain pUSD (no more daily manual drift fix)

Root cause: proxy instability causes order verification calls (get_order,
_shares_held) to fail, leading to untracked fills and persistent
CASH≠CHAIN drift. Required manual state clearing every day.

Fix: in summary() heartbeat, when chain_cash_gap > , automatically set
book cash = chain pUSD balance and record the adjustment. The chain is
the source of truth, not the bot's tracking.

This eliminates the daily manual fix cycle:
  stop bot -> query chain -> set bankroll -> clear state -> restart

The bot now self-heals on every heartbeat (every 60s). If a fill was
untracked, the next heartbeat detects the gap and corrects cash to
match chain reality. Positions on chain are unaffected - only the
cash tracking is anchored.

ponytail: adds one get_balance_allowance call per heartbeat (already
cached for 60s in _chain_bal). Adjustment logged for audit trail.
This commit is contained in:
2026-07-20 15:24:47 +08:00
parent c6e3dd258b
commit b689acbed2
+18
View File
@@ -1715,6 +1715,24 @@ class Copybot:
driftstr = f" · ⚠ LEDGER DRIFT ${drift:+.2f}" if abs(drift) > 0.05 else ""
gap = self.chain_cash_gap()
if gap is not None and abs(gap) > 1.0: # ≥$1: beyond fee/rounding float
# ponytail: auto-anchor book cash to chain pUSD - the chain is the
# source of truth, not the bot's tracking. Proxy instability causes
# order verification calls to fail, leading to untracked fills and
# persistent CASH≠CHAIN drift. Instead of warning and requiring
# manual state clearing every day, correct book cash to match chain.
chain_bal = self._chain_bal[1]
book_cash = self.engine.state.get("cash", bank)
self.engine.state["cash"] = chain_bal
self.engine.state.setdefault("adjustments", []).append({
"ts": time.time(),
"amount": chain_bal - book_cash,
"note": f"chain anchor: cash {book_cash:.2f} -> {chain_bal:.2f}"
})
self.engine.persist()
log(f"chain anchor: book cash ${book_cash:.2f} -> chain ${chain_bal:.2f} "
f"(gap was ${gap:+.2f}, auto-corrected)")
gap = 0 # corrected, don't show the warning
if gap is not None and abs(gap) > 0.5:
driftstr += f" · ⚠ CASH≠CHAIN ${gap:+.2f}"
rtds = getattr(self, "rtds", None)
if rtds is not None: