From b689acbed21157c7bb06a5f874991d5ae9031ee7 Mon Sep 17 00:00:00 2001 From: gavindiaz Date: Mon, 20 Jul 2026 15:24:47 +0800 Subject: [PATCH] fix: auto-anchor book cash to chain pUSD (no more daily manual drift fix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- copybot.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/copybot.py b/copybot.py index 02cbd27e..4ffbd2ec 100644 --- a/copybot.py +++ b/copybot.py @@ -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: