From 85954d8021ff6a954bfb1db65fd288fd4a2caa0f Mon Sep 17 00:00:00 2001 From: jaxperro Date: Fri, 24 Jul 2026 13:18:23 -0400 Subject: [PATCH] =?UTF-8?q?#25=20dark=20flag:=20conviction=5Fscope=20trade?= =?UTF-8?q?|position=20(rolling=20net-USD=20aggregate=20per=20wallet-token?= =?UTF-8?q?;=20clip-built=20conviction=20fires=20at=20the=20floor=20crossi?= =?UTF-8?q?ng).=20Default=20trade=20=E2=80=94=20no=20behavior=20change.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- config.live.example.json | 3 ++- copybot.py | 38 +++++++++++++++++++++++++++++++++++++- live/copybot.paper.json | 3 ++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/config.live.example.json b/config.live.example.json index 0efddf4c..09c66e97 100644 --- a/config.live.example.json +++ b/config.live.example.json @@ -26,7 +26,8 @@ "volume": 0.04, "whale": 0.12 }, - "mirror_sell_min_p": 0.9 + "mirror_sell_min_p": 0.9, + "conviction_scope": "trade" }, "risk": { "max_trade_usd": 1000000.0, diff --git a/copybot.py b/copybot.py index ebd74bc4..4e5158f1 100644 --- a/copybot.py +++ b/copybot.py @@ -243,6 +243,13 @@ FOLLOW_DEFAULT = { "entry_mode": "taker", # "maker" = rest a GTC bid at their price "exit_mode": "mirror", # "hold" = never mirror their sells "maker_ttl_s": 60, # registry-side cancel for resting maker bids + # #25 dark flag (pre-registered): what level the conviction floor checks. + # "trade" = each individual fill must clear the floor (current) + # "position" = a rolling net-USD aggregate per (wallet, token) counts — + # clip-built conviction passes once the aggregate crosses the floor + # (the 1kto1m class: 36 sub-floor-clip positions in one parity week). + # In-memory aggregate: rebuilds from flow after a restart (declared). + "conviction_scope": "trade", } RECENT_TRADE_WINDOW_S = 600 # webhook just told us a trade happened; ignore stale @@ -722,6 +729,26 @@ class FollowFilter: if c == "whale"} wl = cfg.get("watchlist") or [w["wallet"] for w in cfg.get("watch", [])] self.wallets = {w.lower() for w in wl} + # #25: conviction_scope "position" — rolling net-USD per (wallet, + # token), fed by EVERY in-set trade (including sub-floor clips the + # engine never sees). In-memory; rebuilds from flow after restart. + self.scope = f.get("conviction_scope", "trade") + self.agg = {} # (wallet, token) -> [net_usd, last_ts] + + def _agg_update(self, wallet, t): + a = t.get("asset") + if not a: + return 0.0 + usd = t.get("usdcSize") or t.get("size", 0) * t.get("price", 0) or 0 + key = (wallet.lower(), str(a)) + st = self.agg.get(key) or [0.0, 0.0] + st[0] = max(0.0, st[0] + (usd if t.get("side") == "BUY" else -usd)) + st[1] = time.time() + self.agg[key] = st + if len(self.agg) > 8000: # prune stalest tenth, bound memory + for k in sorted(self.agg, key=lambda k: self.agg[k][1])[:800]: + del self.agg[k] + return st[0] def floor(self, wallet): if wallet.lower() in self.whales: @@ -734,12 +761,19 @@ class FollowFilter: return False, "wallet not in follow set" side = t.get("side") if side == "SELL": + if self.scope == "position": + self._agg_update(wallet, t) # sells shrink the aggregate return True, None # engine exits only if we hold if side != "BUY": return False, f"side {side}" usd = t.get("usdcSize") or t.get("size", 0) * t.get("price", 0) fl = self.floor(wallet) - if usd < fl: + if self.scope == "position": + net = self._agg_update(wallet, t) + if usd < fl and net < fl: + return False, (f"clip ${usd:,.0f} · aggregate ${net:,.0f} " + f"< conviction floor ${fl:,.0f}") + elif usd < fl: return False, f"${usd:,.0f} < conviction floor ${fl:,.0f}" p = t.get("price", 0) if not (self.min_entry <= p <= self.max_entry): @@ -752,6 +786,8 @@ class FollowFilter: md = (f" · entry_mode {self.entry_mode} · exit_mode {self.exit_mode}" if (self.entry_mode, self.exit_mode) != ("taker", "mirror") else "") + md += (f" · conviction_scope {self.scope}" + if self.scope != "trade" else "") return (f"follow filter · {'BUY-only' if self.buy_only else 'BUY+SELL'} · " f"conviction ≥ ${self.min_their_usd:,.0f}{pw}{wh} · " f"entry [{self.min_entry:.2f},{self.max_entry:.2f}]{md}") diff --git a/live/copybot.paper.json b/live/copybot.paper.json index a6c29d6e..e4b56831 100644 --- a/live/copybot.paper.json +++ b/live/copybot.paper.json @@ -57,7 +57,8 @@ "volume": 0.04, "whale": 0.12 }, - "mirror_sell_min_p": 0.9 + "mirror_sell_min_p": 0.9, + "conviction_scope": "trade" }, "risk": { "max_trade_usd": 1000000.0,