diff --git a/copybot.py b/copybot.py index 077ce3ab..fb55a8d7 100644 --- a/copybot.py +++ b/copybot.py @@ -1746,6 +1746,13 @@ class Copybot: # without copies (2026-07-13 user report). n = lifetime copies, # n24 = fills in the window. "lag": (lambda t: {"n": lag.get("n", 0), "n24": t[0], + # window empty -> we show the LIFETIME mean, so + # SAY so; the tile used to label it "24h" and + # quietly report a number dragged by the retired + # poll era (2026-07-27 user report) + "basis": ("24h" if t[1] is not None + else "lifetime" if lag.get("n") + else "none"), "avg_s": (round(t[1], 1) if t[1] is not None else (round(lag["sum_s"] / lag["n"], 1) if lag.get("n") else None)), @@ -2347,7 +2354,12 @@ class Copybot: if not rec: return 0, None, None n = len(rec) - return n, sum(r[1] for r in rec) / n, sum(r[2] for r in rec) / n + # MEDIAN, not mean (2026-07-27): one 269s early-rollout outlier made a + # 3s bot read as 25s. The tile is a health signal, not an accounting + # total, so the typical fill is the honest summary. + lags = sorted(r[1] for r in rec) + med = lags[n // 2] if n % 2 else (lags[n // 2 - 1] + lags[n // 2]) / 2 + return n, med, sum(r[2] for r in rec) / n def summary(self, cycle): bank = self.cfg["bankroll_usd"] diff --git a/copytrade.py b/copytrade.py index 9304e284..6e1da1e3 100644 --- a/copytrade.py +++ b/copytrade.py @@ -33,6 +33,12 @@ CLOB_API = "https://clob.polymarket.com" POLYGON_CHAIN_ID = 137 CONFIRM_PHRASE = "TRADE LIVE" +# Polymarket rejects orders under this many shares regardless of dollar +# value ("Size (1.47) lower than the minimum: 5", observed live 2026-07-27). +# It makes the real floor PRICE-dependent: a 90c market needs $4.50, a 20c +# market $1.00 — which is why a $1-stake book can only trade deep longshots. +MIN_SHARES = 5.0 + DEFAULT_CONFIG = { "mode": "paper", # "paper" or "live" "poll_seconds": 12, # how often to check each wallet @@ -431,6 +437,17 @@ class CopyTrader: if allowed < r["min_order_usd"]: return 0.0, (f"capped to ${allowed:.2f} < min order " f"${r['min_order_usd']:.2f} (caps)") + # VENUE SHARE MINIMUM (2026-07-27): Polymarket rejects any order + # under MIN_SHARES shares — "Size (1.47) lower than the minimum: 5" + # — which is a PRICE-dependent dollar floor the $-only gate above + # cannot see. At a $1 stake that caps us to <=20c markets; every + # 40-90c signal fired a doomed order and booked a venue rejection. + # Skip honestly instead, and say what the bet would have needed. + need = MIN_SHARES * price + if allowed < need - 1e-9: + return 0.0, (f"below venue {MIN_SHARES:g}-share minimum: " + f"${allowed:.2f} at {price:.2f} = " + f"{allowed / price:.2f} shares (needs ${need:.2f})") return allowed, None # -- process one of their trades --