From f4d1c2a71feebd153b3bae9c4cd72a6de04ae311 Mon Sep 17 00:00:00 2001 From: gavindiaz Date: Mon, 20 Jul 2026 16:12:46 +0800 Subject: [PATCH] fix: remove broken auto-absorb (caused exponential drift feedback loop) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The auto-absorb added -drift to adjustments, but adjustments are part of the ledger_drift calculation, so each absorb DOUBLED the drift next heartbeat: -> -> -> -> ... -> ,139,590 in 20 minutes. Reverted to only anchoring cash to chain (CASH≠CHAIN fix), which works correctly. LEDGER DRIFT is now just a warning (cosmetic accounting issue that doesn't affect trading). If it bothers you, the manual fix (stop, query chain, clear state, restart) still works. Added NOTE comment explaining why auto-absorbing ledger_drift via adjustments creates a feedback loop. --- copybot.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/copybot.py b/copybot.py index c4716ddf..b0b6367f 100644 --- a/copybot.py +++ b/copybot.py @@ -1713,16 +1713,15 @@ class Copybot: bankstr = f" · banked ${reserve:,.0f}" if reserve else "" drift = self.ledger_drift() gap = self.chain_cash_gap() - # ponytail: auto-anchor to chain - proxy instability causes untracked - # fills leading to LEDGER DRIFT and CASH≠CHAIN. Two corrections: - # 1. If chain cash differs from book cash -> set book cash = chain - # 2. If ledger still drifts after cash correction -> absorb into adjustments - # Ceiling: this masks real bugs if a position is truly untracked, but - # the chain balance already reflects all real positions' cost. - if self.engine.ex.live and gap is not None: + # ponytail: auto-anchor cash to chain when CASH≠CHAIN > $1. Proxy + # instability causes untracked fills; the chain is the source of truth. + # NOTE: do NOT try to auto-fix LEDGER DRIFT via adjustments - it creates + # a feedback loop (adjustment changes the drift calc, which doubles the + # drift next heartbeat, exponentially). LEDGER DRIFT is a cosmetic + # accounting issue that doesn't affect trading; just warn on it. + if self.engine.ex.live and gap is not None and abs(gap) > 1.0: chain_bal = self._chain_bal[1] book_cash = self.engine.state.get("cash", bank) - # 1. anchor cash to chain if chain_bal is not None and abs(chain_bal - book_cash) > 1.0: self.engine.state["cash"] = chain_bal self.engine.state.setdefault("adjustments", []).append({ @@ -1734,18 +1733,6 @@ class Copybot: log(f"chain anchor: book cash ${book_cash:.2f} -> chain ${chain_bal:.2f} " f"(gap ${gap:+.2f}, auto-corrected)") gap = 0 - drift = self.ledger_drift() - # 2. absorb residual ledger drift into adjustments - if abs(drift) > 0.50: - self.engine.state.setdefault("adjustments", []).append({ - "ts": time.time(), - "amount": -drift, - "note": f"auto-absorb ledger drift ${drift:+.2f}" - }) - self.engine.persist() - log(f"auto-absorb ledger drift ${drift:+.2f} (untracked fill, " - f"chain cash OK at ${chain_bal:.2f})") - drift = 0 driftstr = f" · ⚠ LEDGER DRIFT ${drift:+.2f}" if abs(drift) > 0.05 else "" if gap is not None and abs(gap) > 0.5: driftstr += f" · ⚠ CASH≠CHAIN ${gap:+.2f}"